aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/Decoder.java652
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/Signature.java116
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/Variant.java30
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/package-info.java27
-rw-r--r--src/main/java/module-info.java63
-rw-r--r--src/test/java/eu/mulk/jgvariant/core/DecoderTest.java437
6 files changed, 0 insertions, 1325 deletions
diff --git a/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
deleted file mode 100644
index d2f2403..0000000
--- a/src/main/java/eu/mulk/jgvariant/core/Decoder.java
+++ /dev/null
@@ -1,652 +0,0 @@
-package eu.mulk.jgvariant.core;
-
-import static java.nio.ByteOrder.LITTLE_ENDIAN;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.RecordComponent;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import org.apiguardian.api.API;
-import org.apiguardian.api.API.Status;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * 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.
- *
- * <p><strong>Example</strong>
- *
- * <p>To parse a GVariant of type {@code "a(si)"}, which is an array of pairs of {@link String} and
- * {@code int}, you can use the following code:
- *
- * <pre>{@code
- * record ExampleRecord(String s, int i) {}
- *
- * var decoder =
- * Decoder.ofArray(
- * Decoder.ofStructure(
- * ExampleRecord.class,
- * Decoder.ofString(UTF_8),
- * Decoder.ofInt().withByteOrder(LITTLE_ENDIAN)));
- *
- * byte[] bytes = ...;
- * List<ExampleRecord> example = decoder.decode(ByteBuffer.wrap(bytes));
- * }</pre>
- *
- * @param <T> the type that the {@link Decoder} can decode.
- */
-@SuppressWarnings("java:S1610")
-@API(status = Status.EXPERIMENTAL)
-public abstract class Decoder<T> {
-
- private Decoder() {}
-
- /**
- * Decodes a {@link ByteBuffer} holding a serialized GVariant into a value of type {@code T}.
- *
- * <p><strong>Note:</strong> Due to the way the GVariant serialization format works, it is
- * important that the start and end boundaries of the passed byte slice correspond to the actual
- * start and end of the serialized value. The format does generally not allow for the dynamic
- * discovery of the end of the data structure.
- *
- * @param byteSlice a byte slice holding a serialized GVariant.
- * @return the deserialized value.
- * @throws java.nio.BufferUnderflowException if the byte buffer is shorter than the requested
- * data.
- * @throws IllegalArgumentException if the serialized GVariant is ill-formed
- */
- public abstract T decode(ByteBuffer byteSlice);
-
- abstract byte alignment();
-
- @Nullable
- abstract Integer fixedSize();
-
- final boolean hasFixedSize() {
- return fixedSize() != null;
- }
-
- /**
- * Switches the input {@link ByteBuffer} to a given {@link ByteOrder} before reading from it.
- *
- * @param byteOrder the byte order to use.
- * @return a new, decorated {@link Decoder}.
- */
- public Decoder<T> withByteOrder(ByteOrder byteOrder) {
- var delegate = this;
-
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return delegate.alignment();
- }
-
- @Override
- public @Nullable Integer fixedSize() {
- return delegate.fixedSize();
- }
-
- @Override
- public T decode(ByteBuffer byteSlice) {
- byteSlice.order(byteOrder);
- return delegate.decode(byteSlice);
- }
- };
- }
-
- /**
- * Creates a {@link Decoder} for an {@code Array} type.
- *
- * @param elementDecoder a {@link Decoder} for the elements of the array.
- * @param <U> the element type.
- * @return a new {@link Decoder}.
- */
- public static <U> Decoder<List<U>> ofArray(Decoder<U> elementDecoder) {
- return new ArrayDecoder<>(elementDecoder);
- }
-
- /**
- * Creates a {@link Decoder} for a {@code Maybe} type.
- *
- * @param elementDecoder a {@link Decoder} for the contained element.
- * @param <U> the element type.
- * @return a new {@link Decoder}.
- */
- public static <U> Decoder<Optional<U>> ofMaybe(Decoder<U> elementDecoder) {
- return new MaybeDecoder<>(elementDecoder);
- }
-
- /**
- * 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.
- * @param <U> the {@link Record} type that represents the components of the structure.
- * @return a new {@link Decoder}.
- */
- public static <U extends Record> Decoder<U> ofStructure(
- Class<U> recordType, Decoder<?>... componentDecoders) {
- return new StructureDecoder<>(recordType, componentDecoders);
- }
-
- /**
- * 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<Object[]> ofStructure(Decoder<?>... componentDecoders) {
- return new TupleDecoder(componentDecoders);
- }
-
- /**
- * Creates a {@link Decoder} for the {@link Variant} type.
- *
- * <p>The contained {@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>{@code Object[]} (a GVariant structure)
- * <li>{@link Variant} (a nested variant)
- * </ul>
- *
- * @return a new {@link Decoder}.
- */
- public static Decoder<Variant> ofVariant() {
- return new VariantDecoder();
- }
-
- /**
- * Creates a {@link Decoder} for the {@code boolean} type.
- *
- * @return a new {@link Decoder}.
- */
- public static Decoder<Boolean> ofBoolean() {
- return new BooleanDecoder();
- }
-
- /**
- * 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.
- *
- * @return a new {@link Decoder}.
- */
- public static Decoder<Byte> ofByte() {
- return new ByteDecoder();
- }
-
- /**
- * Creates a {@link Decoder} for the 16-bit {@code short} type.
- *
- * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
- * result of this method.
- *
- * @return a new {@link Decoder}.
- */
- public static Decoder<Short> ofShort() {
- return new ShortDecoder();
- }
-
- /**
- * Creates a {@link Decoder} for the 32-bit {@code int} type.
- *
- * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
- * result of this method.
- *
- * @return a new {@link Decoder}.
- */
- public static Decoder<Integer> ofInt() {
- return new IntegerDecoder();
- }
-
- /**
- * Creates a {@link Decoder} for the 64-bit {@code long} type.
- *
- * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
- * result of this method.
- *
- * @return a new {@link Decoder}.
- */
- public static Decoder<Long> ofLong() {
- return new LongDecoder();
- }
-
- /**
- * Creates a {@link Decoder} for the {@code double} type.
- *
- * @return a new {@link Decoder}.
- */
- public static Decoder<Double> ofDouble() {
- return new DoubleDecoder();
- }
-
- /**
- * Creates a {@link Decoder} for the {@link String} type.
- *
- * <p><strong>Note:</strong> While GVariant does not prescribe any particular encoding, {@link
- * java.nio.charset.StandardCharsets#UTF_8} is the most common choice.
- *
- * @param charset the {@link Charset} the string is encoded in.
- * @return a new {@link Decoder}.
- */
- public static Decoder<String> ofString(Charset charset) {
- return new StringDecoder(charset);
- }
-
- private static int align(int offset, byte alignment) {
- return offset % alignment == 0 ? offset : offset + alignment - (offset % alignment);
- }
-
- private static int getIntN(ByteBuffer byteSlice) {
- var intBytes = new byte[4];
- byteSlice.get(intBytes, 0, Math.min(4, byteSlice.limit()));
- return ByteBuffer.wrap(intBytes).order(LITTLE_ENDIAN).getInt();
- }
-
- @SuppressWarnings("java:S3358")
- private static int byteCount(int n) {
- return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4;
- }
-
- private static class ArrayDecoder<U> extends Decoder<List<U>> {
-
- private final Decoder<U> elementDecoder;
-
- ArrayDecoder(Decoder<U> elementDecoder) {
- this.elementDecoder = elementDecoder;
- }
-
- @Override
- public byte alignment() {
- return elementDecoder.alignment();
- }
-
- @Override
- @Nullable
- Integer fixedSize() {
- return null;
- }
-
- @Override
- public List<U> decode(ByteBuffer byteSlice) {
- List<U> elements;
-
- var elementSize = elementDecoder.fixedSize();
- if (elementSize != null) {
- // A simple C-style array.
- elements = new ArrayList<>(byteSlice.limit() / elementSize);
- for (int i = 0; i < byteSlice.limit(); i += elementSize) {
- var element = elementDecoder.decode(byteSlice.slice(i, elementSize));
- elements.add(element);
- }
- } else {
- // An array with aligned elements and a vector of framing offsets in the end.
- int framingOffsetSize = byteCount(byteSlice.limit());
- int lastFramingOffset =
- getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize));
- int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize;
-
- elements = new ArrayList<>(elementCount);
- int position = 0;
- for (int i = 0; i < elementCount; i++) {
- int framingOffset =
- getIntN(
- byteSlice.slice(lastFramingOffset + i * framingOffsetSize, framingOffsetSize));
- elements.add(elementDecoder.decode(byteSlice.slice(position, framingOffset - position)));
- position = align(framingOffset, alignment());
- }
- }
-
- return elements;
- }
- }
-
- private static class MaybeDecoder<U> extends Decoder<Optional<U>> {
-
- private final Decoder<U> elementDecoder;
-
- MaybeDecoder(Decoder<U> elementDecoder) {
- this.elementDecoder = elementDecoder;
- }
-
- @Override
- public byte alignment() {
- return elementDecoder.alignment();
- }
-
- @Override
- @Nullable
- Integer fixedSize() {
- return null;
- }
-
- @Override
- public Optional<U> decode(ByteBuffer byteSlice) {
- if (!byteSlice.hasRemaining()) {
- return Optional.empty();
- } else {
- if (!elementDecoder.hasFixedSize()) {
- // Remove trailing zero byte.
- byteSlice.limit(byteSlice.limit() - 1);
- }
-
- return Optional.of(elementDecoder.decode(byteSlice));
- }
- }
- }
-
- private static class StructureDecoder<U extends Record> extends Decoder<U> {
-
- private final Class<U> recordType;
- 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.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;
- }
-
- @Override
- public byte alignment() {
- return (byte) Arrays.stream(componentDecoders).mapToInt(Decoder::alignment).max().orElse(1);
- }
-
- @Override
- public Integer fixedSize() {
- int position = 0;
- for (var componentDecoder : componentDecoders) {
- var fixedComponentSize = componentDecoder.fixedSize();
- if (fixedComponentSize == null) {
- return null;
- }
-
- position = align(position, componentDecoder.alignment());
- position += fixedComponentSize;
- }
-
- if (position == 0) {
- return 1;
- }
-
- return align(position, alignment());
- }
-
- @Override
- public Object[] decode(ByteBuffer byteSlice) {
- int framingOffsetSize = byteCount(byteSlice.limit());
-
- var objects = new Object[componentDecoders.length];
-
- int position = 0;
- int framingOffsetIndex = 0;
- int componentIndex = 0;
- for (var componentDecoder : componentDecoders) {
- position = align(position, componentDecoder.alignment());
-
- var fixedComponentSize = componentDecoder.fixedSize();
- if (fixedComponentSize != null) {
- objects[componentIndex] =
- componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
- position += fixedComponentSize;
- } else {
- if (componentIndex == componentDecoders.length - 1) {
- // The last component never has a framing offset.
- int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
- objects[componentIndex] =
- componentDecoder.decode(byteSlice.slice(position, endPosition - position));
- position = endPosition;
- } else {
- int framingOffset =
- getIntN(
- byteSlice.slice(
- byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
- framingOffsetSize));
- objects[componentIndex] =
- componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
- position = framingOffset;
- ++framingOffsetIndex;
- }
- }
-
- ++componentIndex;
- }
-
- return objects;
- }
- }
-
- private static class VariantDecoder extends Decoder<Variant> {
-
- @Override
- public byte alignment() {
- return 8;
- }
-
- @Override
- @Nullable
- Integer fixedSize() {
- return null;
- }
-
- @Override
- public Variant decode(ByteBuffer byteSlice) {
- for (int i = byteSlice.limit() - 1; i >= 0; --i) {
- if (byteSlice.get(i) != 0) {
- continue;
- }
-
- var dataBytes = byteSlice.slice(0, i);
- var signatureBytes = byteSlice.slice(i + 1, byteSlice.limit() - (i + 1));
-
- Signature signature;
- try {
- signature = Signature.parse(signatureBytes);
- } catch (ParseException e) {
- throw new IllegalArgumentException(e);
- }
-
- return new Variant(signature, signature.decoder().decode(dataBytes));
- }
-
- throw new IllegalArgumentException("variant signature not found");
- }
- }
-
- private static class BooleanDecoder extends Decoder<Boolean> {
-
- @Override
- public byte alignment() {
- return 1;
- }
-
- @Override
- public Integer fixedSize() {
- return 1;
- }
-
- @Override
- public Boolean decode(ByteBuffer byteSlice) {
- return byteSlice.get() != 0;
- }
- }
-
- private static class ByteDecoder extends Decoder<Byte> {
-
- @Override
- public byte alignment() {
- return 1;
- }
-
- @Override
- public Integer fixedSize() {
- return 1;
- }
-
- @Override
- public Byte decode(ByteBuffer byteSlice) {
- return byteSlice.get();
- }
- }
-
- private static class ShortDecoder extends Decoder<Short> {
-
- @Override
- public byte alignment() {
- return 2;
- }
-
- @Override
- public Integer fixedSize() {
- return 2;
- }
-
- @Override
- public Short decode(ByteBuffer byteSlice) {
- return byteSlice.getShort();
- }
- }
-
- private static class IntegerDecoder extends Decoder<Integer> {
-
- @Override
- public byte alignment() {
- return 4;
- }
-
- @Override
- public Integer fixedSize() {
- return 4;
- }
-
- @Override
- public Integer decode(ByteBuffer byteSlice) {
- return byteSlice.getInt();
- }
- }
-
- private static class LongDecoder extends Decoder<Long> {
-
- @Override
- public byte alignment() {
- return 8;
- }
-
- @Override
- public Integer fixedSize() {
- return 8;
- }
-
- @Override
- public Long decode(ByteBuffer byteSlice) {
- return byteSlice.getLong();
- }
- }
-
- private static class DoubleDecoder extends Decoder<Double> {
-
- @Override
- public byte alignment() {
- return 8;
- }
-
- @Override
- public Integer fixedSize() {
- return 8;
- }
-
- @Override
- public Double decode(ByteBuffer byteSlice) {
- return byteSlice.getDouble();
- }
- }
-
- private static class StringDecoder extends Decoder<String> {
-
- private final Charset charset;
-
- public StringDecoder(Charset charset) {
- this.charset = charset;
- }
-
- @Override
- public byte alignment() {
- return 1;
- }
-
- @Override
- @Nullable
- Integer fixedSize() {
- return null;
- }
-
- @Override
- public String decode(ByteBuffer byteSlice) {
- byteSlice.limit(byteSlice.limit() - 1);
- return charset.decode(byteSlice).toString();
- }
- }
-}
diff --git a/src/main/java/eu/mulk/jgvariant/core/Signature.java b/src/main/java/eu/mulk/jgvariant/core/Signature.java
deleted file mode 100644
index d9de5f1..0000000
--- a/src/main/java/eu/mulk/jgvariant/core/Signature.java
+++ /dev/null
@@ -1,116 +0,0 @@
-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);
- }
-
- 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 'a' -> Decoder.ofArray(parseSignature(signature));
- case '(', '{' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0]));
- default -> throw new ParseException(
- String.format("encountered unknown signature byte '%c'", c), signature.position());
- };
- }
-
- 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;
- }
-}
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 d1c1049..0000000
--- a/src/main/java/eu/mulk/jgvariant/core/Variant.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package eu.mulk.jgvariant.core;
-
-import org.apiguardian.api.API;
-import org.apiguardian.api.API.Status;
-
-/**
- * A dynamically typed GVariant value carrying a {@link Signature} describing its type.
- *
- * <p>{@link #value()} 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 java.util.Optional} (a GVariant {@code Maybe} type)
- * <li>{@link java.util.List} (a GVariant array)
- * <li>{@code Object[]} (a GVariant structure)
- * <li>{@link Variant} (a nested variant)
- * </ul>
- *
- * @param signature the signature describing the type of the value.
- * @param value the value itself; one of {@link Boolean}, {@link Byte}, {@link Short}, {@link
- * Integer}, {@link Long}, {@link String}, {@link java.util.Optional}, {@link java.util.List},
- * {@code Object[]}, {@link Variant}.
- */
-@API(status = Status.EXPERIMENTAL)
-public record Variant(Signature signature, Object value) {}
diff --git a/src/main/java/eu/mulk/jgvariant/core/package-info.java b/src/main/java/eu/mulk/jgvariant/core/package-info.java
deleted file mode 100644
index 1754096..0000000
--- a/src/main/java/eu/mulk/jgvariant/core/package-info.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * 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>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>
- *
- * <p>To parse a GVariant of type {@code "a(si)"}, which is an array of pairs of {@link
- * java.lang.String} and {@code int}, you can use the following code:
- *
- * <pre>{@code
- * record ExampleRecord(String s, int i) {}
- *
- * var decoder =
- * Decoder.ofArray(
- * Decoder.ofStructure(
- * ExampleRecord.class,
- * Decoder.ofString(UTF_8),
- * Decoder.ofInt().withByteOrder(LITTLE_ENDIAN)));
- *
- * byte[] bytes = ...;
- * List<ExampleRecord> example = decoder.decode(ByteBuffer.wrap(bytes));
- * }</pre>
- */
-package eu.mulk.jgvariant.core;
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
deleted file mode 100644
index 0282ff8..0000000
--- a/src/main/java/module-info.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Provides a parser for the <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a>
- * serialization format.
- *
- * <ul>
- * <li><a href="#sect-overview">Overview</a>
- * <li><a href="#sect-installation">Installation</a>
- * </ul>
- *
- * <h2 id="sect-overview">Overview</h2>
- *
- * <p>The {@link eu.mulk.jgvariant.core} package contains the {@link eu.mulk.jgvariant.core.Decoder}
- * type, which contains classes to parse and represent serialized <a
- * href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a> values.
- *
- * <h2 id="sect-installation">Installation</h2>
- *
- * <ul>
- * <li><a href="#sect-installation-maven">Usage with Maven</a>
- * <li><a href="#sect-installation-gradle">Usage with Gradle</a>
- * </ul>
- *
- * <h3 id="sect-installation-maven">Usage with Maven</h3>
- *
- * <pre>{@code
- * <project>
- * ...
- *
- * <dependencies>
- * ...
- *
- * <dependency>
- * <groupId>eu.mulk.jgvariant</groupId>
- * <artifactId>jgvariant-core</artifactId>
- * <version>0.1.3</version>
- * </dependency>
- *
- * ...
- * </dependencies>
- *
- * ...
- * </project>
- * }</pre>
- *
- * <h3 id="sect-installation-gradle">Usage with Gradle</h3>
- *
- * <pre>{@code
- * dependencies {
- * ...
- *
- * implementation("eu.mulk.jgvariant:jgvariant-core:0.1.3")
- *
- * ...
- * }
- * }</pre>
- */
-module eu.mulk.jgvariant.core {
- requires com.google.errorprone.annotations;
- requires org.jetbrains.annotations;
- requires org.apiguardian.api;
-
- exports eu.mulk.jgvariant.core;
-}
diff --git a/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java b/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
deleted file mode 100644
index 5cf1a1c..0000000
--- a/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
+++ /dev/null
@@ -1,437 +0,0 @@
-package eu.mulk.jgvariant.core;
-
-import static java.nio.ByteOrder.BIG_ENDIAN;
-import static java.nio.ByteOrder.LITTLE_ENDIAN;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.nio.ByteBuffer;
-import java.text.ParseException;
-import java.util.List;
-import java.util.Optional;
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests based on the examples given in <a
- * href="https://people.gnome.org/~desrt/gvariant-serialisation.pdf">~desrt/gvariant-serialisation.pdf</a>.
- */
-class DecoderTest {
-
- @Test
- void testString() {
- var data = new byte[] {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x00};
- var decoder = Decoder.ofString(UTF_8);
- assertEquals("hello world", decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testMaybe() {
- var data =
- new byte[] {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x00, 0x00};
- var decoder = Decoder.ofMaybe(Decoder.ofString(UTF_8));
- assertEquals(Optional.of("hello world"), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testBooleanArray() {
- var data = new byte[] {0x01, 0x00, 0x00, 0x01, 0x01};
- var decoder = Decoder.ofArray(Decoder.ofBoolean());
- assertEquals(List.of(true, false, false, true, true), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testStructure() {
- var data =
- new byte[] {
- 0x66, 0x6F, 0x6F, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x04
- };
-
- record TestRecord(String s, int i) {}
-
- var decoder = Decoder.ofStructure(TestRecord.class, Decoder.ofString(UTF_8), Decoder.ofInt());
- assertEquals(new TestRecord("foo", -1), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testComplexStructureArray() {
- var data =
- new byte[] {
- 0x68,
- 0x69,
- 0x00,
- 0x00,
- (byte) 0xfe,
- (byte) 0xff,
- (byte) 0xff,
- (byte) 0xff,
- 0x03,
- 0x00,
- 0x00,
- 0x00,
- 0x62,
- 0x79,
- 0x65,
- 0x00,
- (byte) 0xff,
- (byte) 0xff,
- (byte) 0xff,
- (byte) 0xff,
- 0x04,
- 0x09,
- 0x15
- };
-
- record TestRecord(String s, int i) {}
-
- var decoder =
- Decoder.ofArray(
- Decoder.ofStructure(
- TestRecord.class,
- Decoder.ofString(UTF_8),
- Decoder.ofInt().withByteOrder(LITTLE_ENDIAN)));
- assertEquals(
- List.of(new TestRecord("hi", -2), new TestRecord("bye", -1)),
- decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testStringArray() {
- var data =
- new byte[] {
- 0x69, 0x00, 0x63, 0x61, 0x6E, 0x00, 0x68, 0x61, 0x73, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E,
- 0x67, 0x73, 0x3F, 0x00, 0x02, 0x06, 0x0a, 0x13
- };
- var decoder = Decoder.ofArray(Decoder.ofString(UTF_8));
- assertEquals(List.of("i", "can", "has", "strings?"), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testNestedStructure() {
- var data =
- new byte[] {
- 0x69, 0x63, 0x61, 0x6E, 0x00, 0x68, 0x61, 0x73, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
- 0x73, 0x3F, 0x00, 0x04, 0x0d, 0x05
- };
-
- record TestChild(byte b, String s) {}
- record TestParent(TestChild tc, List<String> as) {}
-
- var decoder =
- Decoder.ofStructure(
- TestParent.class,
- Decoder.ofStructure(TestChild.class, Decoder.ofByte(), Decoder.ofString(UTF_8)),
- Decoder.ofArray(Decoder.ofString(UTF_8)));
-
- assertEquals(
- new TestParent(new TestChild((byte) 0x69, "can"), List.of("has", "strings?")),
- decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testNestedStructureVariant() {
- var data =
- new byte[] {
- 0x69, 0x63, 0x61, 0x6E, 0x00, 0x68, 0x61, 0x73, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
- 0x73, 0x3F, 0x00, 0x04, 0x0d, 0x05, 0x00, 0x28, 0x28, 0x79, 0x73, 0x29, 0x61, 0x73, 0x29
- };
-
- var decoder = Decoder.ofVariant();
- var variant = decoder.decode(ByteBuffer.wrap(data));
- var result = (Object[]) variant.value();
-
- assertAll(
- () -> assertEquals(Signature.parse("((ys)as)"), variant.signature()),
- () -> assertEquals(2, result.length),
- () -> assertArrayEquals(new Object[] {(byte) 0x69, "can"}, (Object[]) result[0]),
- () -> assertEquals(List.of("has", "strings?"), result[1]));
- }
-
- @Test
- void testSimpleStructure() {
- var data = new byte[] {0x60, 0x70};
-
- record TestRecord(byte b1, byte b2) {}
-
- var decoder =
- Decoder.ofStructure(
- TestRecord.class,
- Decoder.ofByte().withByteOrder(LITTLE_ENDIAN),
- Decoder.ofByte().withByteOrder(LITTLE_ENDIAN));
-
- assertEquals(new TestRecord((byte) 0x60, (byte) 0x70), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testPaddedStructureRight() {
- var data = new byte[] {0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00};
-
- record TestRecord(int b1, byte b2) {}
-
- var decoder =
- Decoder.ofStructure(
- TestRecord.class,
- Decoder.ofInt().withByteOrder(LITTLE_ENDIAN),
- Decoder.ofByte().withByteOrder(LITTLE_ENDIAN));
-
- assertEquals(new TestRecord(0x60, (byte) 0x70), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testPaddedStructureLeft() {
- var data = new byte[] {0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00};
-
- record TestRecord(byte b1, int b2) {}
-
- var decoder =
- Decoder.ofStructure(
- TestRecord.class,
- Decoder.ofByte().withByteOrder(LITTLE_ENDIAN),
- Decoder.ofInt().withByteOrder(LITTLE_ENDIAN));
-
- assertEquals(new TestRecord((byte) 0x60, 0x70), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testSimpleStructureArray() {
- var data =
- new byte[] {
- 0x60,
- 0x00,
- 0x00,
- 0x00,
- 0x70,
- 0x00,
- 0x00,
- 0x00,
- (byte) 0x88,
- 0x02,
- 0x00,
- 0x00,
- (byte) 0xF7,
- 0x00,
- 0x00,
- 0x00
- };
-
- record TestRecord(int b1, byte b2) {}
-
- var decoder =
- Decoder.ofArray(
- Decoder.ofStructure(
- TestRecord.class,
- Decoder.ofInt().withByteOrder(LITTLE_ENDIAN),
- Decoder.ofByte().withByteOrder(LITTLE_ENDIAN)));
-
- assertEquals(
- List.of(new TestRecord(96, (byte) 0x70), new TestRecord(648, (byte) 0xf7)),
- decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testByteArray() {
- var data = new byte[] {0x04, 0x05, 0x06, 0x07};
-
- var decoder = Decoder.ofArray(Decoder.ofByte());
-
- assertEquals(
- List.of((byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07),
- decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testIntegerArray() {
- var data = new byte[] {0x04, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00};
-
- var decoder = Decoder.ofArray(Decoder.ofInt().withByteOrder(LITTLE_ENDIAN));
-
- assertEquals(List.of(4, 258), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testDictionaryEntry() {
- var data =
- new byte[] {0x61, 0x20, 0x6B, 0x65, 0x79, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x06};
-
- record TestEntry(String key, int value) {}
-
- var decoder =
- Decoder.ofStructure(
- TestEntry.class, Decoder.ofString(UTF_8), Decoder.ofInt().withByteOrder(LITTLE_ENDIAN));
- assertEquals(new TestEntry("a key", 514), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testPaddedPrimitives() {
- var data =
- new byte[] {
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x40, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
- };
-
- record TestRecord(short s, long l, double d) {}
-
- var decoder =
- Decoder.ofStructure(
- TestRecord.class,
- Decoder.ofShort().withByteOrder(BIG_ENDIAN),
- Decoder.ofLong().withByteOrder(LITTLE_ENDIAN),
- Decoder.ofDouble());
- assertEquals(new TestRecord((short) 1, 2, 3.25), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testEmbeddedMaybe() {
- var data = new byte[] {0x01, 0x01};
-
- record TestRecord(Optional<Byte> set, Optional<Byte> unset) {}
-
- var decoder =
- Decoder.ofStructure(
- TestRecord.class, Decoder.ofMaybe(Decoder.ofByte()), Decoder.ofMaybe(Decoder.ofByte()));
- assertEquals(
- new TestRecord(Optional.of((byte) 1), Optional.empty()),
- decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testRecordComponentMismatch() {
- record TestRecord(Optional<Byte> set) {}
-
- var maybeDecoder = Decoder.ofMaybe(Decoder.ofByte());
- assertThrows(
- IllegalArgumentException.class,
- () -> Decoder.ofStructure(TestRecord.class, maybeDecoder, maybeDecoder));
- }
-
- @Test
- void testTrivialRecord() {
- var data = new byte[] {0x00};
-
- record TestRecord() {}
-
- var decoder = Decoder.ofStructure(TestRecord.class);
- assertEquals(new TestRecord(), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testTwoElementTrivialRecordArray() {
- var data = new byte[] {0x00, 0x00};
-
- record TestRecord() {}
-
- var decoder = Decoder.ofArray(Decoder.ofStructure(TestRecord.class));
- assertEquals(
- List.of(new TestRecord(), new TestRecord()), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testSingletonTrivialRecordArray() {
- var data = new byte[] {0x00};
-
- record TestRecord() {}
-
- var decoder = Decoder.ofArray(Decoder.ofStructure(TestRecord.class));
- assertEquals(List.of(new TestRecord()), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testEmptyTrivialRecordArray() {
- var data = new byte[] {};
-
- record TestRecord() {}
-
- var decoder = Decoder.ofArray(Decoder.ofStructure(TestRecord.class));
- assertEquals(List.of(), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testVariantArray() {
- var data = new byte[] {};
-
- record TestRecord() {}
-
- var decoder = Decoder.ofArray(Decoder.ofStructure(TestRecord.class));
- assertEquals(List.of(), decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testInvalidVariantSignature() {
- var data = new byte[] {0x00, 0x00, 0x2E};
-
- var decoder = Decoder.ofVariant();
- assertThrows(IllegalArgumentException.class, () -> decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testMissingVariantSignature() {
- var data = new byte[] {0x01};
-
- var decoder = Decoder.ofVariant();
- assertThrows(IllegalArgumentException.class, () -> decoder.decode(ByteBuffer.wrap(data)));
- }
-
- @Test
- void testSimpleVariantRecord() throws ParseException {
- // signature: "(bynqiuxtdsogvmiai)"
- var data =
- new byte[] {
- 0x01, // b
- 0x02, // y
- 0x00, 0x03, // n
- 0x00, 0x04, // q
- 0x00, 0x00, // (padding)
- 0x00, 0x00, 0x00, 0x05, // i
- 0x00, 0x00, 0x00, 0x06, // u
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, // x
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // t
- 0x40, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // d
- 0x68, 0x69, 0x00, // s
- 0x68, 0x69, 0x00, // o
- 0x68, 0x69, 0x00, // g
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (padding)
- 0x00, 0x00, 0x00, 0x09, 0x00, 0x69, // v
- 0x00, 0x00, // (padding)
- 0x00, 0x00, 0x00, 0x0a, // mi
- 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, // ai
- 68, 62, 49, 46, 43, // framing offsets
- 0x00, 0x28, 0x62, 0x79, 0x6E, 0x71, 0x69, 0x75, 0x78, 0x74, 0x64, 0x73, 0x6F, 0x67, 0x76,
- 0x6D, 0x69, 0x61, 0x69, 0x29
- };
-
- var decoder = Decoder.ofVariant();
- assertArrayEquals(
- new Object[] {
- true,
- (byte) 2,
- (short) 3,
- (short) 4,
- (int) 5,
- (int) 6,
- (long) 7,
- (long) 8,
- (double) 3.25,
- "hi",
- "hi",
- "hi",
- new Variant(Signature.parse("i"), 9),
- Optional.of(10),
- List.of(11, 12)
- },
- (Object[]) decoder.decode(ByteBuffer.wrap(data)).value());
- }
-
- @Test
- void testSignatureString() throws ParseException {
- var data =
- new byte[] {
- 0x28, 0x62, 0x79, 0x6E, 0x71, 0x69, 0x75, 0x78, 0x74, 0x64, 0x73, 0x6F, 0x67, 0x76, 0x6D,
- 0x69, 0x61, 0x69, 0x29
- };
-
- var signature = Signature.parse(ByteBuffer.wrap(data));
- assertEquals("(bynqiuxtdsogvmiai)", signature.toString());
- }
-}