blob: bb479ff82fb50f75c4531dfa8f60444fe4abcd01 [file] [log] [blame]
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +01001package eu.mulk.jgvariant.core;
2
3import static java.nio.ByteOrder.LITTLE_ENDIAN;
4
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +01005import java.lang.reflect.InvocationTargetException;
6import java.lang.reflect.RecordComponent;
7import java.nio.ByteBuffer;
8import java.nio.ByteOrder;
9import java.nio.charset.Charset;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.List;
13import java.util.Optional;
14import org.jetbrains.annotations.Nullable;
15
16/**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +010017 * Type class for decodable {@link Variant} types.
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +010018 *
19 * <p>Use the {@code of*} family of constructor methods to acquire a suitable {@link Decoder} for
20 * the type you wish to decode.
21 *
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010022 * <p><strong>Example</strong>
23 *
24 * <p>To parse a GVariant of type {@code "a(si)"}, which is an array of pairs of {@link String} and
25 * {@code int}, you can use the following code:
26 *
27 * <pre>{@code
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +010028 * record ExampleRecord(String s, int i) {}
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010029 *
30 * var decoder =
31 * Decoder.ofArray(
32 * Decoder.ofStructure(
33 * ExampleRecord.class,
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +010034 * Decoder.ofString(UTF_8),
35 * Decoder.ofInt().withByteOrder(LITTLE_ENDIAN)));
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010036 *
37 * byte[] bytes = ...;
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +010038 * List<ExampleRecord> example = decoder.decode(ByteBuffer.wrap(bytes));
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010039 * }</pre>
40 *
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +010041 * @param <T> the type that the {@link Decoder} can decode.
42 */
43@SuppressWarnings("java:S1610")
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +010044public abstract class Decoder<T> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +010045
46 private Decoder() {}
47
48 /**
49 * @throws java.nio.BufferUnderflowException if the byte buffer is shorter than the requested
50 * data.
51 */
52 public abstract T decode(ByteBuffer byteSlice);
53
54 abstract byte alignment();
55
56 @Nullable
57 abstract Integer fixedSize();
58
59 final boolean hasFixedSize() {
60 return fixedSize() != null;
61 }
62
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010063 /**
64 * Switches the input {@link ByteBuffer} to a given {@link ByteOrder} before reading from it.
65 *
66 * @param byteOrder the byte order to use.
67 * @return a new, decorated {@link Decoder}.
68 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +010069 public Decoder<T> withByteOrder(ByteOrder byteOrder) {
70 var delegate = this;
71
72 return new Decoder<>() {
73 @Override
74 public byte alignment() {
75 return delegate.alignment();
76 }
77
78 @Override
79 public @Nullable Integer fixedSize() {
80 return delegate.fixedSize();
81 }
82
83 @Override
84 public T decode(ByteBuffer byteSlice) {
85 byteSlice.order(byteOrder);
86 return delegate.decode(byteSlice);
87 }
88 };
89 }
90
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010091 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +010092 * Creates a {@link Decoder} for an {@code Array} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010093 *
94 * @param elementDecoder a {@link Decoder} for the elements of the array.
95 * @param <U> the element type.
96 * @return a new {@link Decoder}.
97 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +010098 public static <U> Decoder<List<U>> ofArray(Decoder<U> elementDecoder) {
Matthias Andreas Benkard34430182021-12-14 20:00:36 +010099 return new ArrayDecoder<>(elementDecoder);
100 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100101
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100102 /**
103 * Creates a {@link Decoder} for a {@code Maybe} type.
104 *
105 * @param elementDecoder a {@link Decoder} for the contained element.
106 * @param <U> the element type.
107 * @return a new {@link Decoder}.
108 */
109 public static <U> Decoder<Optional<U>> ofMaybe(Decoder<U> elementDecoder) {
110 return new MaybeDecoder<>(elementDecoder);
111 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100112
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100113 /**
114 * Creates a {@link Decoder} for a {@code Structure} type.
115 *
116 * @param recordType the {@link Record} type that represents the components of the structure.
117 * @param componentDecoders a {@link Decoder} for each component of the structure.
118 * @param <U> the {@link Record} type that represents the components of the structure.
119 * @return a new {@link Decoder}.
120 */
121 public static <U extends Record> Decoder<U> ofStructure(
122 Class<U> recordType, Decoder<?>... componentDecoders) {
123 return new StructureDecoder<>(recordType, componentDecoders);
124 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100125
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100126 /**
127 * Creates a {@link Decoder} for the {@link Variant} type.
128 *
129 * @return a new {@link Decoder}.
130 */
131 public static Decoder<Variant> ofVariant() {
132 return new VariantDecoder();
133 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100134
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100135 /**
136 * Creates a {@link Decoder} for the {@code Boolean} type.
137 *
138 * @return a new {@link Decoder}.
139 */
140 public static Decoder<Boolean> ofBoolean() {
141 return new BooleanDecoder();
142 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100143
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100144 /**
145 * Creates a {@link Decoder} for the 8-bit {@ode byte} type.
146 *
147 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
148 * result of this method.
149 *
150 * @return a new {@link Decoder}.
151 */
152 public static Decoder<Byte> ofByte() {
153 return new ByteDecoder();
154 }
155
156 /**
157 * Creates a {@link Decoder} for the 16-bit {@code short} type.
158 *
159 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
160 * result of this method.
161 *
162 * @return a new {@link Decoder}.
163 */
164 public static Decoder<Short> ofShort() {
165 return new ShortDecoder();
166 }
167
168 /**
169 * Creates a {@link Decoder} for the 32-bit {@code int} type.
170 *
171 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
172 * result of this method.
173 *
174 * @return a new {@link Decoder}.
175 */
176 public static Decoder<Integer> ofInt() {
177 return new IntegerDecoder();
178 }
179
180 /**
181 * Creates a {@link Decoder} for the 64-bit {@code long} type.
182 *
183 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
184 * result of this method.
185 *
186 * @return a new {@link Decoder}.
187 */
188 public static Decoder<Long> ofLong() {
189 return new LongDecoder();
190 }
191
192 /**
193 * Creates a {@link Decoder} for the {@code double} type.
194 *
195 * @return a new {@link Decoder}.
196 */
197 public static Decoder<Double> ofDouble() {
198 return new DoubleDecoder();
199 }
200
201 /**
202 * Creates a {@link Decoder} for the {@link String} type.
203 *
204 * <p><strong>Note:</strong> While GVariant does not prescribe any particular encoding, {@link
205 * java.nio.charset.StandardCharsets#UTF_8} is the most common choice.
206 *
207 * @return a new {@link Decoder}.
208 */
209 public static Decoder<String> ofString(Charset charset) {
210 return new StringDecoder(charset);
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100211 }
212
213 private static int align(int offset, byte alignment) {
214 return offset % alignment == 0 ? offset : offset + alignment - (offset % alignment);
215 }
216
217 private static int getIntN(ByteBuffer byteSlice) {
218 var intBytes = new byte[4];
219 byteSlice.get(intBytes, 0, Math.min(4, byteSlice.limit()));
220 return ByteBuffer.wrap(intBytes).order(LITTLE_ENDIAN).getInt();
221 }
222
223 @SuppressWarnings("java:S3358")
224 private static int byteCount(int n) {
225 return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4;
226 }
227
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100228 private static class ArrayDecoder<U> extends Decoder<List<U>> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100229
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100230 private final Decoder<U> elementDecoder;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100231
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100232 ArrayDecoder(Decoder<U> elementDecoder) {
233 this.elementDecoder = elementDecoder;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100234 }
235
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100236 @Override
237 public byte alignment() {
238 return elementDecoder.alignment();
239 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100240
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100241 @Override
242 @Nullable
243 Integer fixedSize() {
244 return null;
245 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100246
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100247 @Override
248 public List<U> decode(ByteBuffer byteSlice) {
249 List<U> elements;
250
251 var elementSize = elementDecoder.fixedSize();
252 if (elementSize != null) {
253 // A simple C-style array.
254 elements = new ArrayList<>(byteSlice.limit() / elementSize);
255 for (int i = 0; i < byteSlice.limit(); i += elementSize) {
256 var element = elementDecoder.decode(byteSlice.slice(i, elementSize));
257 elements.add(element);
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100258 }
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100259 } else {
260 // An array with aligned elements and a vector of framing offsets in the end.
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100261 int framingOffsetSize = byteCount(byteSlice.limit());
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100262 int lastFramingOffset =
263 getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize));
264 int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100265
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100266 elements = new ArrayList<>(elementCount);
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100267 int position = 0;
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100268 for (int i = 0; i < elementCount; i++) {
269 int framingOffset =
270 getIntN(
271 byteSlice.slice(lastFramingOffset + i * framingOffsetSize, framingOffsetSize));
272 elements.add(elementDecoder.decode(byteSlice.slice(position, framingOffset - position)));
273 position = align(framingOffset, alignment());
274 }
275 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100276
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100277 return elements;
278 }
279 }
280
281 private static class MaybeDecoder<U> extends Decoder<Optional<U>> {
282
283 private final Decoder<U> elementDecoder;
284
285 MaybeDecoder(Decoder<U> elementDecoder) {
286 this.elementDecoder = elementDecoder;
287 }
288
289 @Override
290 public byte alignment() {
291 return elementDecoder.alignment();
292 }
293
294 @Override
295 @Nullable
296 Integer fixedSize() {
297 return null;
298 }
299
300 @Override
301 public Optional<U> decode(ByteBuffer byteSlice) {
302 if (!byteSlice.hasRemaining()) {
303 return Optional.empty();
304 } else {
305 if (!elementDecoder.hasFixedSize()) {
306 // Remove trailing zero byte.
307 byteSlice.limit(byteSlice.limit() - 1);
308 }
309
310 return Optional.of(elementDecoder.decode(byteSlice));
311 }
312 }
313 }
314
315 private static class StructureDecoder<U extends Record> extends Decoder<U> {
316
317 private final RecordComponent[] recordComponents;
318 private final Class<U> recordType;
319 private final Decoder<?>[] componentDecoders;
320
321 StructureDecoder(Class<U> recordType, Decoder<?>... componentDecoders) {
322 var recordComponents = recordType.getRecordComponents();
323
324 if (componentDecoders.length != recordComponents.length) {
325 throw new IllegalArgumentException(
326 "number of decoders (%d) does not match number of structure components (%d)"
327 .formatted(componentDecoders.length, recordComponents.length));
328 }
329
330 this.recordComponents = recordComponents;
331 this.recordType = recordType;
332 this.componentDecoders = componentDecoders;
333 }
334
335 @Override
336 public byte alignment() {
337 return (byte) Arrays.stream(componentDecoders).mapToInt(Decoder::alignment).max().orElse(1);
338 }
339
340 @Override
341 public Integer fixedSize() {
342 int position = 0;
343 for (var componentDecoder : componentDecoders) {
344 var fixedComponentSize = componentDecoder.fixedSize();
345 if (fixedComponentSize == null) {
346 return null;
347 }
348
349 position = align(position, componentDecoder.alignment());
350 position += fixedComponentSize;
351 }
352
353 if (position == 0) {
354 return 1;
355 }
356
357 return align(position, alignment());
358 }
359
360 @Override
361 public U decode(ByteBuffer byteSlice) {
362 int framingOffsetSize = byteCount(byteSlice.limit());
363
364 var recordConstructorArguments = new Object[recordComponents.length];
365
366 int position = 0;
367 int framingOffsetIndex = 0;
368 int componentIndex = 0;
369 for (var componentDecoder : componentDecoders) {
370 position = align(position, componentDecoder.alignment());
371
372 var fixedComponentSize = componentDecoder.fixedSize();
373 if (fixedComponentSize != null) {
374 recordConstructorArguments[componentIndex] =
375 componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
376 position += fixedComponentSize;
377 } else {
378 if (componentIndex == recordComponents.length - 1) {
379 // The last component never has a framing offset.
380 int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100381 recordConstructorArguments[componentIndex] =
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100382 componentDecoder.decode(byteSlice.slice(position, endPosition - position));
383 position = endPosition;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100384 } else {
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100385 int framingOffset =
386 getIntN(
387 byteSlice.slice(
388 byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
389 framingOffsetSize));
390 recordConstructorArguments[componentIndex] =
391 componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
392 position = framingOffset;
393 ++framingOffsetIndex;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100394 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100395 }
396
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100397 ++componentIndex;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100398 }
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100399
400 try {
401 var recordComponentTypes =
402 Arrays.stream(recordType.getRecordComponents())
403 .map(RecordComponent::getType)
404 .toArray(Class<?>[]::new);
405 var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes);
406 return recordConstructor.newInstance(recordConstructorArguments);
407 } catch (NoSuchMethodException
408 | InstantiationException
409 | IllegalAccessException
410 | InvocationTargetException e) {
411 throw new IllegalStateException(e);
412 }
413 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100414 }
415
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100416 private static class VariantDecoder extends Decoder<Variant> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100417
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100418 @Override
419 public byte alignment() {
420 return 8;
421 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100422
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100423 @Override
424 @Nullable
425 Integer fixedSize() {
426 return null;
427 }
428
429 @Override
430 public Variant decode(ByteBuffer byteSlice) {
431 // TODO
432 throw new UnsupportedOperationException("not implemented");
433 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100434 }
435
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100436 private static class BooleanDecoder extends Decoder<Boolean> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100437
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100438 @Override
439 public byte alignment() {
440 return 1;
441 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100442
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100443 @Override
444 public Integer fixedSize() {
445 return 1;
446 }
447
448 @Override
449 public Boolean decode(ByteBuffer byteSlice) {
450 return byteSlice.get() != 0;
451 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100452 }
453
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100454 private static class ByteDecoder extends Decoder<Byte> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100455
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100456 @Override
457 public byte alignment() {
458 return 1;
459 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100460
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100461 @Override
462 public Integer fixedSize() {
463 return 1;
464 }
465
466 @Override
467 public Byte decode(ByteBuffer byteSlice) {
468 return byteSlice.get();
469 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100470 }
471
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100472 private static class ShortDecoder extends Decoder<Short> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100473
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100474 @Override
475 public byte alignment() {
476 return 2;
477 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100478
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100479 @Override
480 public Integer fixedSize() {
481 return 2;
482 }
483
484 @Override
485 public Short decode(ByteBuffer byteSlice) {
486 return byteSlice.getShort();
487 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100488 }
489
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100490 private static class IntegerDecoder extends Decoder<Integer> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100491
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100492 @Override
493 public byte alignment() {
494 return 4;
495 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100496
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100497 @Override
498 public Integer fixedSize() {
499 return 4;
500 }
501
502 @Override
503 public Integer decode(ByteBuffer byteSlice) {
504 return byteSlice.getInt();
505 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100506 }
507
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100508 private static class LongDecoder extends Decoder<Long> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100509
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100510 @Override
511 public byte alignment() {
512 return 8;
513 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100514
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100515 @Override
516 public Integer fixedSize() {
517 return 8;
518 }
519
520 @Override
521 public Long decode(ByteBuffer byteSlice) {
522 return byteSlice.getLong();
523 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100524 }
525
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100526 private static class DoubleDecoder extends Decoder<Double> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100527
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100528 @Override
529 public byte alignment() {
530 return 8;
531 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100532
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100533 @Override
534 public Integer fixedSize() {
535 return 8;
536 }
537
538 @Override
539 public Double decode(ByteBuffer byteSlice) {
540 return byteSlice.getDouble();
541 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100542 }
543
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100544 private static class StringDecoder extends Decoder<String> {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100545
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100546 private final Charset charset;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100547
Matthias Andreas Benkard34430182021-12-14 20:00:36 +0100548 public StringDecoder(Charset charset) {
549 this.charset = charset;
550 }
551
552 @Override
553 public byte alignment() {
554 return 1;
555 }
556
557 @Override
558 @Nullable
559 Integer fixedSize() {
560 return null;
561 }
562
563 @Override
564 public String decode(ByteBuffer byteSlice) {
565 byteSlice.limit(byteSlice.limit() - 1);
566 return charset.decode(byteSlice).toString();
567 }
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100568 }
569}