diff options
author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2021-12-14 21:51:10 +0100 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2021-12-14 21:51:10 +0100 |
commit | 55c34814bc7e6749a1530e7b36b51e0bc6358df3 (patch) | |
tree | 6405ff6b5904dc96dd27ecea98ef2b59fa30a4d4 /src/main/java | |
parent | 34430180f83a9e52ead4f919731f955bbc3b3b79 (diff) |
Remove Variant class, parse variants into Object.
Change-Id: I9b4b3079aea42b74f6fcf6341305b6fded9234f4
Diffstat (limited to 'src/main/java')
-rw-r--r-- | src/main/java/eu/mulk/jgvariant/core/Decoder.java | 161 | ||||
-rw-r--r-- | src/main/java/eu/mulk/jgvariant/core/Variant.java | 102 | ||||
-rw-r--r-- | src/main/java/eu/mulk/jgvariant/core/package-info.java | 13 | ||||
-rw-r--r-- | src/main/java/module-info.java | 6 |
4 files changed, 135 insertions, 147 deletions
diff --git a/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/src/main/java/eu/mulk/jgvariant/core/Decoder.java index bb479ff..9833998 100644 --- a/src/main/java/eu/mulk/jgvariant/core/Decoder.java +++ b/src/main/java/eu/mulk/jgvariant/core/Decoder.java @@ -7,6 +7,7 @@ import java.lang.reflect.RecordComponent; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -14,7 +15,7 @@ import java.util.Optional; import org.jetbrains.annotations.Nullable; /** - * Type class for decodable {@link Variant} types. + * Type class for decodable types. * * <p>Use the {@code of*} family of constructor methods to acquire a suitable {@link Decoder} for * the type you wish to decode. @@ -111,7 +112,7 @@ public abstract class Decoder<T> { } /** - * Creates a {@link Decoder} for a {@code Structure} type. + * Creates a {@link Decoder} for a {@code Structure} type, decoding into a {@link Record}. * * @param recordType the {@link Record} type that represents the components of the structure. * @param componentDecoders a {@link Decoder} for each component of the structure. @@ -124,11 +125,38 @@ public abstract class Decoder<T> { } /** - * Creates a {@link Decoder} for the {@link Variant} type. + * Creates a {@link Decoder} for a {@code Structure} type, decoding into a {@link List}. * + * <p>Prefer {@link #ofStructure(Class, Decoder[])} if possible, which is both more type-safe and + * more convenient. + * + * @param componentDecoders a {@link Decoder} for each component of the structure. * @return a new {@link Decoder}. */ - public static Decoder<Variant> ofVariant() { + public static Decoder<Object[]> ofStructure(Decoder<?>... componentDecoders) { + return new TupleDecoder(componentDecoders); + } + + /** + * Creates a {@link Decoder} for the {@code Variant} type. + * + * <p>The returned {@link Object} can be of one of the following types: + * + * <ul> + * <li>{@link Boolean} + * <li>{@link Byte} + * <li>{@link Short} + * <li>{@link Integer} + * <li>{@link Long} + * <li>{@link String} + * <li>{@link Optional} (a GVariant {@code Maybe} type) + * <li>{@link List} (a GVariant array) + * <li>{@link Object[]} (a GVariant structure) + * </ul> + * + * @return a new {@link Decoder}. + */ + public static Decoder<Object> ofVariant() { return new VariantDecoder(); } @@ -142,7 +170,7 @@ public abstract class Decoder<T> { } /** - * Creates a {@link Decoder} for the 8-bit {@ode byte} type. + * Creates a {@link Decoder} for the 8-bit {@code byte} type. * * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the * result of this method. @@ -314,21 +342,56 @@ public abstract class Decoder<T> { private static class StructureDecoder<U extends Record> extends Decoder<U> { - private final RecordComponent[] recordComponents; private final Class<U> recordType; - private final Decoder<?>[] componentDecoders; + private final TupleDecoder tupleDecoder; StructureDecoder(Class<U> recordType, Decoder<?>... componentDecoders) { var recordComponents = recordType.getRecordComponents(); - if (componentDecoders.length != recordComponents.length) { throw new IllegalArgumentException( "number of decoders (%d) does not match number of structure components (%d)" .formatted(componentDecoders.length, recordComponents.length)); } - this.recordComponents = recordComponents; this.recordType = recordType; + this.tupleDecoder = new TupleDecoder(componentDecoders); + } + + @Override + public byte alignment() { + return tupleDecoder.alignment(); + } + + @Override + public Integer fixedSize() { + return tupleDecoder.fixedSize(); + } + + @Override + public U decode(ByteBuffer byteSlice) { + Object[] recordConstructorArguments = tupleDecoder.decode(byteSlice); + + try { + var recordComponentTypes = + Arrays.stream(recordType.getRecordComponents()) + .map(RecordComponent::getType) + .toArray(Class<?>[]::new); + var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes); + return recordConstructor.newInstance(recordConstructorArguments); + } catch (NoSuchMethodException + | InstantiationException + | IllegalAccessException + | InvocationTargetException e) { + throw new IllegalStateException(e); + } + } + } + + private static class TupleDecoder extends Decoder<Object[]> { + + private final Decoder<?>[] componentDecoders; + + TupleDecoder(Decoder<?>... componentDecoders) { this.componentDecoders = componentDecoders; } @@ -358,10 +421,10 @@ public abstract class Decoder<T> { } @Override - public U decode(ByteBuffer byteSlice) { + public Object[] decode(ByteBuffer byteSlice) { int framingOffsetSize = byteCount(byteSlice.limit()); - var recordConstructorArguments = new Object[recordComponents.length]; + var objects = new Object[componentDecoders.length]; int position = 0; int framingOffsetIndex = 0; @@ -371,14 +434,14 @@ public abstract class Decoder<T> { var fixedComponentSize = componentDecoder.fixedSize(); if (fixedComponentSize != null) { - recordConstructorArguments[componentIndex] = + objects[componentIndex] = componentDecoder.decode(byteSlice.slice(position, fixedComponentSize)); position += fixedComponentSize; } else { - if (componentIndex == recordComponents.length - 1) { + if (componentIndex == componentDecoders.length - 1) { // The last component never has a framing offset. int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize; - recordConstructorArguments[componentIndex] = + objects[componentIndex] = componentDecoder.decode(byteSlice.slice(position, endPosition - position)); position = endPosition; } else { @@ -387,7 +450,7 @@ public abstract class Decoder<T> { byteSlice.slice( byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize, framingOffsetSize)); - recordConstructorArguments[componentIndex] = + objects[componentIndex] = componentDecoder.decode(byteSlice.slice(position, framingOffset - position)); position = framingOffset; ++framingOffsetIndex; @@ -397,23 +460,11 @@ public abstract class Decoder<T> { ++componentIndex; } - try { - var recordComponentTypes = - Arrays.stream(recordType.getRecordComponents()) - .map(RecordComponent::getType) - .toArray(Class<?>[]::new); - var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes); - return recordConstructor.newInstance(recordConstructorArguments); - } catch (NoSuchMethodException - | InstantiationException - | IllegalAccessException - | InvocationTargetException e) { - throw new IllegalStateException(e); - } + return objects; } } - private static class VariantDecoder extends Decoder<Variant> { + private static class VariantDecoder extends Decoder<Object> { @Override public byte alignment() { @@ -427,9 +478,55 @@ public abstract class Decoder<T> { } @Override - public Variant decode(ByteBuffer byteSlice) { - // TODO - throw new UnsupportedOperationException("not implemented"); + public Object decode(ByteBuffer byteSlice) { + for (int i = byteSlice.limit() - 1; i >= 0; --i) { + if (byteSlice.get(i) != 0) { + continue; + } + + var data = byteSlice.slice(0, i); + var signature = byteSlice.slice(i + 1, byteSlice.limit() - (i + 1)); + + Decoder<?> decoder = parseSignature(signature); + return decoder.decode(data); + } + + throw new IllegalArgumentException("variant signature not found"); + } + + private static Decoder<?> parseSignature(ByteBuffer signature) { + 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 'a' -> Decoder.ofArray(parseSignature(signature)); + case '(', '{' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0])); + default -> throw new IllegalArgumentException( + String.format("encountered unknown signature byte '%c'", c)); + }; + } + + private static List<Decoder<?>> parseTupleTypes(ByteBuffer signature) { + 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; } } diff --git a/src/main/java/eu/mulk/jgvariant/core/Variant.java b/src/main/java/eu/mulk/jgvariant/core/Variant.java deleted file mode 100644 index 05e28d5..0000000 --- a/src/main/java/eu/mulk/jgvariant/core/Variant.java +++ /dev/null @@ -1,102 +0,0 @@ -package eu.mulk.jgvariant.core; - -import java.util.List; -import java.util.Optional; - -/** - * A value representable by the <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a> - * serialization format, tagged with its type. - * - * <p>{@link Variant} is a sum type (sealed interface) that represents a GVariant value. Its - * subtypes represent the different types of values that GVariant supports. - * - * @see Decoder#ofVariant() - */ -public sealed interface Variant { - - /** - * A homogeneous sequence of GVariant values. - * - * <p>Arrays of fixed width (i.e. of values of fixed size) are represented in a similar way to - * plain C arrays. Arrays of variable width require additional space for padding and framing. - * - * <p>Heterogeneous sequences are represented by {@code Array<Variant>}. - * - * @param <T> the type of the elements of the array. - * @see Decoder#ofArray - */ - record Array<T>(List<T> values) implements Variant {} - - /** - * A value that is either present or absent. - * - * @param <T> the contained type. - * @see Decoder#ofMaybe - */ - record Maybe<T>(Optional<T> value) implements Variant {} - - /** - * A tuple of values of fixed types. - * - * <p>GVariant structures are represented as {@link Record} types. For example, a two-element - * structure consisting of a string and an int can be modelled as follows: - * - * <pre>{@code - * record TestRecord(String s, int i) {} - * var testStruct = new Structure<>(new TestRecord("hello", 123); - * }</pre> - * - * @param <T> the {@link Record} type that represents the components of the structure. - * @see Decoder#ofStructure - */ - record Structure<T extends Record>(T values) implements Variant {} - - /** - * Either true or false. - * - * @see Decoder#ofBoolean() - */ - record Bool(boolean value) implements Variant {} - - /** - * A {@code byte}-sized integer. - * - * @see Decoder#ofByte() - */ - record Byte(byte value) implements Variant {} - - /** - * A {@code short}-sized integer. - * - * @see Decoder#ofShort() - */ - record Short(short value) implements Variant {} - - /** - * An {@code int}-sized integer. - * - * @see Decoder#ofInt() - */ - record Int(int value) implements Variant {} - - /** - * A {@code long}-sized integer. - * - * @see Decoder#ofLong() - */ - record Long(long value) implements Variant {} - - /** - * A double-precision floating point number. - * - * @see Decoder#ofDouble() - */ - record Double(double value) implements Variant {} - - /** - * A character string. - * - * @see Decoder#ofString - */ - record String(java.lang.String value) implements Variant {} -} diff --git a/src/main/java/eu/mulk/jgvariant/core/package-info.java b/src/main/java/eu/mulk/jgvariant/core/package-info.java index 1b819c5..1754096 100644 --- a/src/main/java/eu/mulk/jgvariant/core/package-info.java +++ b/src/main/java/eu/mulk/jgvariant/core/package-info.java @@ -1,14 +1,9 @@ /** - * Provides {@link eu.mulk.jgvariant.core.Variant} and {@link eu.mulk.jgvariant.core.Decoder}, the - * foundational classes for <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a> - * parsing. + * Provides {@link eu.mulk.jgvariant.core.Decoder}, the foundational class for <a + * href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a> parsing. * - * <p>{@link eu.mulk.jgvariant.core.Variant} is a sum type (sealed interface) that represents a - * GVariant value. Its subtypes represent the different types of values that GVariant supports. - * - * <p>Instances of {@link eu.mulk.jgvariant.core.Decoder} read a given concrete subtype of {@link - * eu.mulk.jgvariant.core.Variant} from a {@link java.nio.ByteBuffer}. The class also contains - * factory methods to create those instances. + * <p>Instances of {@link eu.mulk.jgvariant.core.Decoder} read a given value type from a {@link + * java.nio.ByteBuffer}. The class also contains factory methods to create those instances. * * <p><strong>Example</strong> * diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index af28413..39e91b8 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,11 +1,9 @@ -import eu.mulk.jgvariant.core.Variant; - /** * Provides a parser for the <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a> * serialization format. * - * <p>The {@link eu.mulk.jgvariant.core} package contains the {@link Variant} and {@link - * eu.mulk.jgvariant.core.Decoder} types. which form the foundation of this library. + * <p>The {@link eu.mulk.jgvariant.core} package contains the {@link eu.mulk.jgvariant.core.Decoder} + * type. which forms the foundation of this library. */ module eu.mulk.jgvariant.core { requires com.google.errorprone.annotations; |