aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md43
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/Decoder.java104
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/Value.java83
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/package-info.java32
-rw-r--r--src/main/java/module-info.java8
-rw-r--r--src/test/java/eu/mulk/jgvariant/core/DecoderTest.java2
6 files changed, 266 insertions, 6 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e482857
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+# GVariant for Java
+
+This library provides a [GVariant][] parser in pure Java.
+
+
+## Overview
+
+The two foundational classes are `Value` and `Decoder`.
+
+`Value` is a sum type (sealed interface) that represents a
+[GVariant][] value. Its subtypes represent the different types of
+values that [GVariant][] supports.
+
+Instances of `Decoder` read a given concrete subtype of `Value` from a
+[ByteBuffer][]. The class also contains factory methods to create
+those instances.
+
+The various subclasses of `Decoder` together implement the [GVariant
+serialization][] specification.
+
+
+## Example
+
+To parse a [GVariant][] value of type `"a(si)"`, which is an array of
+pairs of [String][] and `int`, you can use the following code:
+
+ record ExampleRecord(Value.Str s, Value.Int32 i) {}
+
+ var decoder =
+ Decoder.ofArray(
+ Decoder.ofStructure(
+ ExampleRecord.class,
+ Decoder.ofStr(StandardCharsets.UTF_8),
+ Decoder.ofInt32().withByteOrder(ByteOrder.LITTLE_ENDIAN)));
+
+ byte[] bytes = ...;
+ Value.Array<Value.Structure<ExampleRecord>> example = decoder.decode(bytes);
+
+
+[ByteBuffer]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/ByteBuffer.html
+[GVariant]: https://docs.gtk.org/glib/struct.Variant.html
+[GVariant serialization]: https://people.gnome.org/~desrt/gvariant-serialisation.pdf
+[String]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html
diff --git a/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
index 2ebd0af..c51a723 100644
--- a/src/main/java/eu/mulk/jgvariant/core/Decoder.java
+++ b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
@@ -30,6 +30,25 @@ import org.jetbrains.annotations.Nullable;
* <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(Value.Str s, Value.Int32 i) {}
+ *
+ * var decoder =
+ * Decoder.ofArray(
+ * Decoder.ofStructure(
+ * ExampleRecord.class,
+ * Decoder.ofStr(UTF_8),
+ * Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN)));
+ *
+ * byte[] bytes = ...;
+ * Value.Array<Value.Structure<ExampleRecord>> example = decoder.decode(ByteBuffer.wrap(bytes));
+ * }</pre>
+ *
* @param <T> the type that the {@link Decoder} can decode.
*/
@SuppressWarnings("java:S1610")
@@ -52,6 +71,12 @@ public abstract class Decoder<T extends Value> {
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;
@@ -74,6 +99,13 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for an {@link 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 extends Value> Decoder<Array<U>> ofArray(Decoder<U> elementDecoder) {
return new Decoder<>() {
@Override
@@ -138,6 +170,13 @@ public abstract class Decoder<T extends Value> {
return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4;
}
+ /**
+ * Creates a {@link Decoder} for a {@link Maybe} type.
+ *
+ * @param elementDecoder a {@link Decoder} for the contained element.
+ * @param <U> the element type.
+ * @return a new {@link Decoder}.
+ */
public static <U extends Value> Decoder<Maybe<U>> ofMaybe(Decoder<U> elementDecoder) {
return new Decoder<>() {
@Override
@@ -167,6 +206,14 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for a {@link Structure} type.
+ *
+ * @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}.
+ */
@SafeVarargs
public static <U extends Record> Decoder<Structure<U>> ofStructure(
Class<U> recordType, Decoder<? extends Value>... componentDecoders) {
@@ -260,6 +307,11 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for the {@link Variant} type.
+ *
+ * @return a new {@link Decoder}.
+ */
public static Decoder<Variant> ofVariant() {
return new Decoder<>() {
@Override
@@ -281,7 +333,12 @@ public abstract class Decoder<T extends Value> {
};
}
- public static Decoder<Bool> ofBoolean() {
+ /**
+ * Creates a {@link Decoder} for the {@link Bool} type.
+ *
+ * @return a new {@link Decoder}.
+ */
+ public static Decoder<Bool> ofBool() {
return new Decoder<>() {
@Override
public byte alignment() {
@@ -300,6 +357,14 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for the {@link Int8} 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<Int8> ofInt8() {
return new Decoder<>() {
@Override
@@ -319,6 +384,14 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for the {@link Int16} 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<Int16> ofInt16() {
return new Decoder<>() {
@Override
@@ -338,6 +411,14 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for the {@link Int32} 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<Int32> ofInt32() {
return new Decoder<>() {
@Override
@@ -357,6 +438,14 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for the {@link Int64} 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<Int64> ofInt64() {
return new Decoder<>() {
@Override
@@ -376,6 +465,11 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for the {@link Float64} type.
+ *
+ * @return a new {@link Decoder}.
+ */
public static Decoder<Float64> ofFloat64() {
return new Decoder<>() {
@Override
@@ -395,6 +489,14 @@ public abstract class Decoder<T extends Value> {
};
}
+ /**
+ * Creates a {@link Decoder} for the {@link Str} 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.
+ *
+ * @return a new {@link Decoder}.
+ */
public static Decoder<Str> ofStr(Charset charset) {
return new Decoder<>() {
@Override
diff --git a/src/main/java/eu/mulk/jgvariant/core/Value.java b/src/main/java/eu/mulk/jgvariant/core/Value.java
index 969f240..54c0b79 100644
--- a/src/main/java/eu/mulk/jgvariant/core/Value.java
+++ b/src/main/java/eu/mulk/jgvariant/core/Value.java
@@ -3,33 +3,110 @@ package eu.mulk.jgvariant.core;
import java.util.List;
import java.util.Optional;
-/** A value representable by the GVariant serialization format. */
+/**
+ * A value representable by the <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a>
+ * serialization format.
+ *
+ * <p>{@link Value} is a sum type (sealed interface) that represents a GVariant value. Its subtypes
+ * represent the different types of values that GVariant supports.
+ *
+ * @see Decoder
+ */
public sealed interface Value {
- // Composite types
+ /**
+ * 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 extends Value>(List<T> values) implements Value {}
+ /**
+ * A value that is either present or absent.
+ *
+ * @param <T> the contained type.
+ * @see Decoder#ofMaybe
+ */
record Maybe<T extends Value>(Optional<T> value) implements Value {}
+ /**
+ * 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(Str s, Int32 i) {}
+ * var testStruct = new Structure<>(new TestRecord(new Str("hello"), new Int32(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 Value {}
+ /**
+ * A dynamically typed box that can hold a single value of any GVariant type.
+ *
+ * @see Decoder#ofVariant
+ */
record Variant(Class<? extends Value> type, Value value) implements Value {}
- // Primitive types
+ /**
+ * Either true or false.
+ *
+ * @see Decoder#ofBool()
+ */
record Bool(boolean value) implements Value {
static Bool TRUE = new Bool(true);
static Bool FALSE = new Bool(false);
}
+ /**
+ * A {@code byte}-sized integer.
+ *
+ * @see Decoder#ofInt8()
+ */
record Int8(byte value) implements Value {}
+ /**
+ * A {@code short}-sized integer.
+ *
+ * @see Decoder#ofInt16()
+ */
record Int16(short value) implements Value {}
+ /**
+ * An {@code int}-sized integer.
+ *
+ * @see Decoder#ofInt32()
+ */
record Int32(int value) implements Value {}
+ /**
+ * A {@code long}-sized integer.
+ *
+ * @see Decoder#ofInt64()
+ */
record Int64(long value) implements Value {}
+ /**
+ * A double-precision floating point number.
+ *
+ * @see Decoder#ofFloat64()
+ */
record Float64(double value) implements Value {}
+ /**
+ * A character string.
+ *
+ * @see Decoder#ofStr
+ */
record Str(String value) implements 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
new file mode 100644
index 0000000..dba7a09
--- /dev/null
+++ b/src/main/java/eu/mulk/jgvariant/core/package-info.java
@@ -0,0 +1,32 @@
+/**
+ * Provides {@link eu.mulk.jgvariant.core.Value} and {@link eu.mulk.jgvariant.core.Decoder}, the
+ * foundational classes for <a href="https://docs.gtk.org/glib/struct.Variant.html">GVariant</a>
+ * parsing.
+ *
+ * <p>{@link eu.mulk.jgvariant.core.Value} 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.Value} 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 String} and
+ * {@code int}, you can use the following code:
+ *
+ * <pre>{@code
+ * record ExampleRecord(Value.Str s, Value.Int32 i) {}
+ *
+ * var decoder =
+ * Decoder.ofArray(
+ * Decoder.ofStructure(
+ * ExampleRecord.class,
+ * Decoder.ofStr(UTF_8),
+ * Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN)));
+ *
+ * byte[] bytes = ...;
+ * Value.Array<Value.Structure<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
index 8880c0b..403cf1f 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,5 +1,11 @@
+/**
+ * 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 eu.mulk.jgvariant.core.Value}
+ * and {@link eu.mulk.jgvariant.core.Decoder} types. which form the foundation of this library.
+ */
module eu.mulk.jgvariant.core {
- requires java.base;
requires com.google.errorprone.annotations;
requires org.jetbrains.annotations;
diff --git a/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java b/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
index 4deb8bc..13cd398 100644
--- a/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
+++ b/src/test/java/eu/mulk/jgvariant/core/DecoderTest.java
@@ -41,7 +41,7 @@ class DecoderTest {
@Test
void testBooleanArray() {
var data = new byte[] {0x01, 0x00, 0x00, 0x01, 0x01};
- var decoder = Decoder.ofArray(Decoder.ofBoolean());
+ var decoder = Decoder.ofArray(Decoder.ofBool());
assertEquals(
new Array<>(List.of(Bool.TRUE, Bool.FALSE, Bool.FALSE, Bool.TRUE, Bool.TRUE)),
decoder.decode(ByteBuffer.wrap(data)));