blob: c69773016b82f84eaf45336ee3a7210f02c1c549 [file] [log] [blame]
package eu.mulk.jgvariant.core;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
/**
* A GVariant signature string.
*
* <p>Describes a type in the GVariant type system. The type can be arbitrarily complex.
*
* <p><strong>Examples</strong>
*
* <dl>
* <dt>{@code "i"}
* <dd>a single 32-bit integer
* <dt>{@code "ai"}
* <dd>an array of 32-bit integers
* <dt>{@code "(bbb(sai))"}
* <dd>a record consisting of three booleans and a nested record, which consists of a string and
* an array of 32-bit integers
* </dl>
*/
@API(status = Status.STABLE)
public final class Signature {
private final String signatureString;
private final Decoder<?> decoder;
Signature(ByteBuffer signatureBytes) throws ParseException {
this.decoder = parseSignature(signatureBytes);
signatureBytes.rewind();
this.signatureString = StandardCharsets.US_ASCII.decode(signatureBytes).toString();
}
static Signature parse(ByteBuffer signatureBytes) throws ParseException {
return new Signature(signatureBytes);
}
public static Signature parse(String signatureString) throws ParseException {
var signatureBytes = ByteBuffer.wrap(signatureString.getBytes(StandardCharsets.US_ASCII));
return parse(signatureBytes);
}
/**
* Returns a {@link Decoder} that can decode values conforming to this signature.
*
* @return a {@link Decoder} for this signature
*/
@SuppressWarnings("unchecked")
Decoder<Object> decoder() {
return (Decoder<Object>) decoder;
}
/**
* Returns the signature formatted as a GVariant signature string.
*
* @return a GVariant signature string.
*/
@Override
public String toString() {
return signatureString;
}
@Override
public boolean equals(Object o) {
return (o instanceof Signature signature)
&& Objects.equals(signatureString, signature.signatureString);
}
@Override
public int hashCode() {
return Objects.hash(signatureString);
}
private static Decoder<?> parseSignature(ByteBuffer signature) throws ParseException {
char c = (char) signature.get();
return switch (c) {
case 'b' -> Decoder.ofBoolean();
case 'y' -> Decoder.ofByte();
case 'n', 'q' -> Decoder.ofShort();
case 'i', 'u' -> Decoder.ofInt();
case 'x', 't' -> Decoder.ofLong();
case 'd' -> Decoder.ofDouble();
case 's', 'o', 'g' -> Decoder.ofString(StandardCharsets.UTF_8);
case 'v' -> Decoder.ofVariant();
case 'm' -> Decoder.ofMaybe(parseSignature(signature));
case '(' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0]));
case 'a' -> {
char elementC = (char) signature.get(signature.position());
if (elementC == '{') {
signature.get();
List<Decoder<?>> entryTypes = parseDictionaryEntryTypes(signature);
yield Decoder.ofDictionary(entryTypes.get(0), entryTypes.get(1));
} else {
yield Decoder.ofArray(parseSignature(signature));
}
}
case '{' -> {
List<Decoder<?>> entryTypes = parseDictionaryEntryTypes(signature);
yield Decoder.ofDictionaryEntry(entryTypes.get(0), entryTypes.get(1));
}
default -> throw new ParseException(
String.format("encountered unknown signature byte '%c'", c), signature.position());
};
}
private static List<Decoder<?>> parseDictionaryEntryTypes(ByteBuffer signature)
throws ParseException {
var tupleTypes = parseTupleTypes(signature);
if (tupleTypes.size() != 2) {
throw new ParseException(
String.format("dictionary entry type with %d components, expected 2", tupleTypes.size()),
signature.position());
}
return tupleTypes;
}
private static List<Decoder<?>> parseTupleTypes(ByteBuffer signature) throws ParseException {
List<Decoder<?>> decoders = new ArrayList<>();
while (true) {
char c = (char) signature.get(signature.position());
if (c == ')' || c == '}') {
signature.get();
break;
}
decoders.add(parseSignature(signature));
}
return decoders;
}
}