blob: 8134d45d1252d2a5d337c0cf05f19e65b9dfc96e [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 Benkard261532a2021-12-12 20:09:27 +010099 return new Decoder<>() {
100 @Override
101 public byte alignment() {
102 return elementDecoder.alignment();
103 }
104
105 @Override
106 @Nullable
107 Integer fixedSize() {
108 return null;
109 }
110
111 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100112 public List<U> decode(ByteBuffer byteSlice) {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100113 List<U> elements;
114
115 var elementSize = elementDecoder.fixedSize();
116 if (elementSize != null) {
117 // A simple C-style array.
118 elements = new ArrayList<>(byteSlice.limit() / elementSize);
119 for (int i = 0; i < byteSlice.limit(); i += elementSize) {
120 var element = elementDecoder.decode(byteSlice.slice(i, elementSize));
121 elements.add(element);
122 }
123 } else {
124 // An array with aligned elements and a vector of framing offsets in the end.
125 int framingOffsetSize = byteCount(byteSlice.limit());
126 int lastFramingOffset =
127 getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize));
128 int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize;
129
130 elements = new ArrayList<>(elementCount);
131 int position = 0;
132 for (int i = 0; i < elementCount; i++) {
133 int framingOffset =
134 getIntN(
135 byteSlice.slice(lastFramingOffset + i * framingOffsetSize, framingOffsetSize));
136 elements.add(
137 elementDecoder.decode(byteSlice.slice(position, framingOffset - position)));
138 position = align(framingOffset, alignment());
139 }
140 }
141
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100142 return elements;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100143 }
144 };
145 }
146
147 private static int align(int offset, byte alignment) {
148 return offset % alignment == 0 ? offset : offset + alignment - (offset % alignment);
149 }
150
151 private static int getIntN(ByteBuffer byteSlice) {
152 var intBytes = new byte[4];
153 byteSlice.get(intBytes, 0, Math.min(4, byteSlice.limit()));
154 return ByteBuffer.wrap(intBytes).order(LITTLE_ENDIAN).getInt();
155 }
156
157 @SuppressWarnings("java:S3358")
158 private static int byteCount(int n) {
159 return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4;
160 }
161
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100162 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100163 * Creates a {@link Decoder} for a {@code Maybe} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100164 *
165 * @param elementDecoder a {@link Decoder} for the contained element.
166 * @param <U> the element type.
167 * @return a new {@link Decoder}.
168 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100169 public static <U> Decoder<Optional<U>> ofMaybe(Decoder<U> elementDecoder) {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100170 return new Decoder<>() {
171 @Override
172 public byte alignment() {
173 return elementDecoder.alignment();
174 }
175
176 @Override
177 @Nullable
178 Integer fixedSize() {
179 return null;
180 }
181
182 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100183 public Optional<U> decode(ByteBuffer byteSlice) {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100184 if (!byteSlice.hasRemaining()) {
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100185 return Optional.empty();
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100186 } else {
187 if (!elementDecoder.hasFixedSize()) {
188 // Remove trailing zero byte.
189 byteSlice.limit(byteSlice.limit() - 1);
190 }
191
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100192 return Optional.of(elementDecoder.decode(byteSlice));
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100193 }
194 }
195 };
196 }
197
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100198 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100199 * Creates a {@link Decoder} for a {@code Structure} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100200 *
201 * @param recordType the {@link Record} type that represents the components of the structure.
202 * @param componentDecoders a {@link Decoder} for each component of the structure.
203 * @param <U> the {@link Record} type that represents the components of the structure.
204 * @return a new {@link Decoder}.
205 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100206 public static <U extends Record> Decoder<U> ofStructure(
207 Class<U> recordType, Decoder<?>... componentDecoders) {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100208 var recordComponents = recordType.getRecordComponents();
209 if (componentDecoders.length != recordComponents.length) {
210 throw new IllegalArgumentException(
211 "number of decoders (%d) does not match number of structure components (%d)"
212 .formatted(componentDecoders.length, recordComponents.length));
213 }
214
215 return new Decoder<>() {
216 @Override
217 public byte alignment() {
218 return (byte) Arrays.stream(componentDecoders).mapToInt(Decoder::alignment).max().orElse(1);
219 }
220
221 @Override
222 public Integer fixedSize() {
223 int position = 0;
224 for (var componentDecoder : componentDecoders) {
225 var fixedComponentSize = componentDecoder.fixedSize();
226 if (fixedComponentSize == null) {
227 return null;
228 }
229
230 position = align(position, componentDecoder.alignment());
231 position += fixedComponentSize;
232 }
233
234 if (position == 0) {
235 return 1;
236 }
237
238 return align(position, alignment());
239 }
240
241 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100242 public U decode(ByteBuffer byteSlice) {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100243 int framingOffsetSize = byteCount(byteSlice.limit());
244
245 var recordConstructorArguments = new Object[recordComponents.length];
246
247 int position = 0;
248 int framingOffsetIndex = 0;
249 int componentIndex = 0;
250 for (var componentDecoder : componentDecoders) {
251 position = align(position, componentDecoder.alignment());
252
253 var fixedComponentSize = componentDecoder.fixedSize();
254 if (fixedComponentSize != null) {
255 recordConstructorArguments[componentIndex] =
256 componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
257 position += fixedComponentSize;
258 } else {
259 if (componentIndex == recordComponents.length - 1) {
260 // The last component never has a framing offset.
261 int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
262 recordConstructorArguments[componentIndex] =
263 componentDecoder.decode(byteSlice.slice(position, endPosition - position));
264 position = endPosition;
265 } else {
266 int framingOffset =
267 getIntN(
268 byteSlice.slice(
269 byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
270 framingOffsetSize));
271 recordConstructorArguments[componentIndex] =
272 componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
273 position = framingOffset;
274 ++framingOffsetIndex;
275 }
276 }
277
278 ++componentIndex;
279 }
280
281 try {
282 var recordComponentTypes =
283 Arrays.stream(recordType.getRecordComponents())
284 .map(RecordComponent::getType)
285 .toArray(Class<?>[]::new);
286 var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes);
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100287 return recordConstructor.newInstance(recordConstructorArguments);
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100288 } catch (NoSuchMethodException
289 | InstantiationException
290 | IllegalAccessException
291 | InvocationTargetException e) {
292 throw new IllegalStateException(e);
293 }
294 }
295 };
296 }
297
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100298 /**
299 * Creates a {@link Decoder} for the {@link Variant} type.
300 *
301 * @return a new {@link Decoder}.
302 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100303 public static Decoder<Variant> ofVariant() {
304 return new Decoder<>() {
305 @Override
306 public byte alignment() {
307 return 8;
308 }
309
310 @Override
311 @Nullable
312 Integer fixedSize() {
313 return null;
314 }
315
316 @Override
317 public Variant decode(ByteBuffer byteSlice) {
318 // TODO
319 throw new UnsupportedOperationException("not implemented");
320 }
321 };
322 }
323
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100324 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100325 * Creates a {@link Decoder} for the {@code Boolean} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100326 *
327 * @return a new {@link Decoder}.
328 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100329 public static Decoder<Boolean> ofBoolean() {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100330 return new Decoder<>() {
331 @Override
332 public byte alignment() {
333 return 1;
334 }
335
336 @Override
337 public Integer fixedSize() {
338 return 1;
339 }
340
341 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100342 public Boolean decode(ByteBuffer byteSlice) {
343 return byteSlice.get() != 0;
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100344 }
345 };
346 }
347
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100348 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100349 * Creates a {@link Decoder} for the 8-bit {@ode byte} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100350 *
351 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
352 * result of this method.
353 *
354 * @return a new {@link Decoder}.
355 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100356 public static Decoder<Byte> ofByte() {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100357 return new Decoder<>() {
358 @Override
359 public byte alignment() {
360 return 1;
361 }
362
363 @Override
364 public Integer fixedSize() {
365 return 1;
366 }
367
368 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100369 public Byte decode(ByteBuffer byteSlice) {
370 return byteSlice.get();
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100371 }
372 };
373 }
374
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100375 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100376 * Creates a {@link Decoder} for the 16-bit {@code short} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100377 *
378 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
379 * result of this method.
380 *
381 * @return a new {@link Decoder}.
382 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100383 public static Decoder<Short> ofShort() {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100384 return new Decoder<>() {
385 @Override
386 public byte alignment() {
387 return 2;
388 }
389
390 @Override
391 public Integer fixedSize() {
392 return 2;
393 }
394
395 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100396 public Short decode(ByteBuffer byteSlice) {
397 return byteSlice.getShort();
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100398 }
399 };
400 }
401
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100402 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100403 * Creates a {@link Decoder} for the 32-bit {@code int} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100404 *
405 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
406 * result of this method.
407 *
408 * @return a new {@link Decoder}.
409 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100410 public static Decoder<Integer> ofInt() {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100411 return new Decoder<>() {
412 @Override
413 public byte alignment() {
414 return 4;
415 }
416
417 @Override
418 public Integer fixedSize() {
419 return 4;
420 }
421
422 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100423 public Integer decode(ByteBuffer byteSlice) {
424 return byteSlice.getInt();
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100425 }
426 };
427 }
428
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100429 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100430 * Creates a {@link Decoder} for the 64-bit {@code long} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100431 *
432 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
433 * result of this method.
434 *
435 * @return a new {@link Decoder}.
436 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100437 public static Decoder<Long> ofLong() {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100438 return new Decoder<>() {
439 @Override
440 public byte alignment() {
441 return 8;
442 }
443
444 @Override
445 public Integer fixedSize() {
446 return 8;
447 }
448
449 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100450 public Long decode(ByteBuffer byteSlice) {
451 return byteSlice.getLong();
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100452 }
453 };
454 }
455
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100456 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100457 * Creates a {@link Decoder} for the {@code double} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100458 *
459 * @return a new {@link Decoder}.
460 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100461 public static Decoder<Double> ofDouble() {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100462 return new Decoder<>() {
463 @Override
464 public byte alignment() {
465 return 8;
466 }
467
468 @Override
469 public Integer fixedSize() {
470 return 8;
471 }
472
473 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100474 public Double decode(ByteBuffer byteSlice) {
475 return byteSlice.getDouble();
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100476 }
477 };
478 }
479
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100480 /**
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100481 * Creates a {@link Decoder} for the {@link String} type.
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100482 *
483 * <p><strong>Note:</strong> While GVariant does not prescribe any particular encoding, {@link
484 * java.nio.charset.StandardCharsets#UTF_8} is the most common choice.
485 *
486 * @return a new {@link Decoder}.
487 */
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100488 public static Decoder<String> ofString(Charset charset) {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100489 return new Decoder<>() {
490 @Override
491 public byte alignment() {
492 return 1;
493 }
494
495 @Override
496 @Nullable
497 Integer fixedSize() {
498 return null;
499 }
500
501 @Override
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100502 public String decode(ByteBuffer byteSlice) {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100503 byteSlice.limit(byteSlice.limit() - 1);
Matthias Andreas Benkard35f7a202021-12-14 19:29:26 +0100504 return charset.decode(byteSlice).toString();
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100505 }
506 };
507 }
508}