aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/eu/mulk/jgvariant/core/Decoder.java663
1 files changed, 362 insertions, 301 deletions
diff --git a/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
index 8134d45..bb479ff 100644
--- a/src/main/java/eu/mulk/jgvariant/core/Decoder.java
+++ b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
@@ -96,67 +96,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static <U> Decoder<List<U>> ofArray(Decoder<U> elementDecoder) {
- return new Decoder<>() {
- @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 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;
+ return new ArrayDecoder<>(elementDecoder);
}
/**
@@ -167,32 +107,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static <U> Decoder<Optional<U>> ofMaybe(Decoder<U> elementDecoder) {
- return new Decoder<>() {
- @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));
- }
- }
- };
+ return new MaybeDecoder<>(elementDecoder);
}
/**
@@ -205,94 +120,7 @@ public abstract class Decoder<T> {
*/
public static <U extends Record> Decoder<U> ofStructure(
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));
- }
-
- return new Decoder<>() {
- @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 U decode(ByteBuffer byteSlice) {
- int framingOffsetSize = byteCount(byteSlice.limit());
-
- var recordConstructorArguments = new Object[recordComponents.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) {
- recordConstructorArguments[componentIndex] =
- componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
- position += fixedComponentSize;
- } else {
- if (componentIndex == recordComponents.length - 1) {
- // The last component never has a framing offset.
- int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
- recordConstructorArguments[componentIndex] =
- componentDecoder.decode(byteSlice.slice(position, endPosition - position));
- position = endPosition;
- } else {
- int framingOffset =
- getIntN(
- byteSlice.slice(
- byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
- framingOffsetSize));
- recordConstructorArguments[componentIndex] =
- componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
- position = framingOffset;
- ++framingOffsetIndex;
- }
- }
-
- ++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 new StructureDecoder<>(recordType, componentDecoders);
}
/**
@@ -301,24 +129,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<Variant> ofVariant() {
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return 8;
- }
-
- @Override
- @Nullable
- Integer fixedSize() {
- return null;
- }
-
- @Override
- public Variant decode(ByteBuffer byteSlice) {
- // TODO
- throw new UnsupportedOperationException("not implemented");
- }
- };
+ return new VariantDecoder();
}
/**
@@ -327,22 +138,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<Boolean> ofBoolean() {
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return 1;
- }
-
- @Override
- public Integer fixedSize() {
- return 1;
- }
-
- @Override
- public Boolean decode(ByteBuffer byteSlice) {
- return byteSlice.get() != 0;
- }
- };
+ return new BooleanDecoder();
}
/**
@@ -354,22 +150,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<Byte> ofByte() {
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return 1;
- }
-
- @Override
- public Integer fixedSize() {
- return 1;
- }
-
- @Override
- public Byte decode(ByteBuffer byteSlice) {
- return byteSlice.get();
- }
- };
+ return new ByteDecoder();
}
/**
@@ -381,22 +162,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<Short> ofShort() {
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return 2;
- }
-
- @Override
- public Integer fixedSize() {
- return 2;
- }
-
- @Override
- public Short decode(ByteBuffer byteSlice) {
- return byteSlice.getShort();
- }
- };
+ return new ShortDecoder();
}
/**
@@ -408,22 +174,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<Integer> ofInt() {
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return 4;
- }
-
- @Override
- public Integer fixedSize() {
- return 4;
- }
-
- @Override
- public Integer decode(ByteBuffer byteSlice) {
- return byteSlice.getInt();
- }
- };
+ return new IntegerDecoder();
}
/**
@@ -435,22 +186,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<Long> ofLong() {
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return 8;
- }
-
- @Override
- public Integer fixedSize() {
- return 8;
- }
-
- @Override
- public Long decode(ByteBuffer byteSlice) {
- return byteSlice.getLong();
- }
- };
+ return new LongDecoder();
}
/**
@@ -459,22 +195,7 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<Double> ofDouble() {
- return new Decoder<>() {
- @Override
- public byte alignment() {
- return 8;
- }
-
- @Override
- public Integer fixedSize() {
- return 8;
- }
-
- @Override
- public Double decode(ByteBuffer byteSlice) {
- return byteSlice.getDouble();
- }
- };
+ return new DoubleDecoder();
}
/**
@@ -486,23 +207,363 @@ public abstract class Decoder<T> {
* @return a new {@link Decoder}.
*/
public static Decoder<String> ofString(Charset charset) {
- return new Decoder<>() {
- @Override
- public byte alignment() {
+ 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 RecordComponent[] recordComponents;
+ private final Class<U> recordType;
+ private final Decoder<?>[] componentDecoders;
+
+ 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.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;
}
- @Override
- @Nullable
- Integer fixedSize() {
- return null;
+ return align(position, alignment());
+ }
+
+ @Override
+ public U decode(ByteBuffer byteSlice) {
+ int framingOffsetSize = byteCount(byteSlice.limit());
+
+ var recordConstructorArguments = new Object[recordComponents.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) {
+ recordConstructorArguments[componentIndex] =
+ componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
+ position += fixedComponentSize;
+ } else {
+ if (componentIndex == recordComponents.length - 1) {
+ // The last component never has a framing offset.
+ int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
+ recordConstructorArguments[componentIndex] =
+ componentDecoder.decode(byteSlice.slice(position, endPosition - position));
+ position = endPosition;
+ } else {
+ int framingOffset =
+ getIntN(
+ byteSlice.slice(
+ byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
+ framingOffsetSize));
+ recordConstructorArguments[componentIndex] =
+ componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
+ position = framingOffset;
+ ++framingOffsetIndex;
+ }
+ }
+
+ ++componentIndex;
}
- @Override
- public String decode(ByteBuffer byteSlice) {
- byteSlice.limit(byteSlice.limit() - 1);
- return charset.decode(byteSlice).toString();
+ 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 VariantDecoder extends Decoder<Variant> {
+
+ @Override
+ public byte alignment() {
+ return 8;
+ }
+
+ @Override
+ @Nullable
+ Integer fixedSize() {
+ return null;
+ }
+
+ @Override
+ public Variant decode(ByteBuffer byteSlice) {
+ // TODO
+ throw new UnsupportedOperationException("not implemented");
+ }
+ }
+
+ 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();
+ }
}
}