blob: c51a7232b1a179bc03d2593816d1f8b304568255 [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
5import eu.mulk.jgvariant.core.Value.Array;
6import eu.mulk.jgvariant.core.Value.Bool;
7import eu.mulk.jgvariant.core.Value.Float64;
8import eu.mulk.jgvariant.core.Value.Int16;
9import eu.mulk.jgvariant.core.Value.Int32;
10import eu.mulk.jgvariant.core.Value.Int64;
11import eu.mulk.jgvariant.core.Value.Int8;
12import eu.mulk.jgvariant.core.Value.Maybe;
13import eu.mulk.jgvariant.core.Value.Str;
14import eu.mulk.jgvariant.core.Value.Structure;
15import eu.mulk.jgvariant.core.Value.Variant;
16import java.lang.reflect.InvocationTargetException;
17import java.lang.reflect.RecordComponent;
18import java.nio.ByteBuffer;
19import java.nio.ByteOrder;
20import java.nio.charset.Charset;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.List;
24import java.util.Optional;
25import org.jetbrains.annotations.Nullable;
26
27/**
28 * Type class for decodable {@link Value} types.
29 *
30 * <p>Use the {@code of*} family of constructor methods to acquire a suitable {@link Decoder} for
31 * the type you wish to decode.
32 *
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010033 * <p><strong>Example</strong>
34 *
35 * <p>To parse a GVariant of type {@code "a(si)"}, which is an array of pairs of {@link String} and
36 * {@code int}, you can use the following code:
37 *
38 * <pre>{@code
39 * record ExampleRecord(Value.Str s, Value.Int32 i) {}
40 *
41 * var decoder =
42 * Decoder.ofArray(
43 * Decoder.ofStructure(
44 * ExampleRecord.class,
45 * Decoder.ofStr(UTF_8),
46 * Decoder.ofInt32().withByteOrder(LITTLE_ENDIAN)));
47 *
48 * byte[] bytes = ...;
49 * Value.Array<Value.Structure<ExampleRecord>> example = decoder.decode(ByteBuffer.wrap(bytes));
50 * }</pre>
51 *
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +010052 * @param <T> the type that the {@link Decoder} can decode.
53 */
54@SuppressWarnings("java:S1610")
55public abstract class Decoder<T extends Value> {
56
57 private Decoder() {}
58
59 /**
60 * @throws java.nio.BufferUnderflowException if the byte buffer is shorter than the requested
61 * data.
62 */
63 public abstract T decode(ByteBuffer byteSlice);
64
65 abstract byte alignment();
66
67 @Nullable
68 abstract Integer fixedSize();
69
70 final boolean hasFixedSize() {
71 return fixedSize() != null;
72 }
73
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +010074 /**
75 * Switches the input {@link ByteBuffer} to a given {@link ByteOrder} before reading from it.
76 *
77 * @param byteOrder the byte order to use.
78 * @return a new, decorated {@link Decoder}.
79 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +010080 public Decoder<T> withByteOrder(ByteOrder byteOrder) {
81 var delegate = this;
82
83 return new Decoder<>() {
84 @Override
85 public byte alignment() {
86 return delegate.alignment();
87 }
88
89 @Override
90 public @Nullable Integer fixedSize() {
91 return delegate.fixedSize();
92 }
93
94 @Override
95 public T decode(ByteBuffer byteSlice) {
96 byteSlice.order(byteOrder);
97 return delegate.decode(byteSlice);
98 }
99 };
100 }
101
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100102 /**
103 * Creates a {@link Decoder} for an {@link Array} type.
104 *
105 * @param elementDecoder a {@link Decoder} for the elements of the array.
106 * @param <U> the element type.
107 * @return a new {@link Decoder}.
108 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100109 public static <U extends Value> Decoder<Array<U>> ofArray(Decoder<U> elementDecoder) {
110 return new Decoder<>() {
111 @Override
112 public byte alignment() {
113 return elementDecoder.alignment();
114 }
115
116 @Override
117 @Nullable
118 Integer fixedSize() {
119 return null;
120 }
121
122 @Override
123 public Array<U> decode(ByteBuffer byteSlice) {
124 List<U> elements;
125
126 var elementSize = elementDecoder.fixedSize();
127 if (elementSize != null) {
128 // A simple C-style array.
129 elements = new ArrayList<>(byteSlice.limit() / elementSize);
130 for (int i = 0; i < byteSlice.limit(); i += elementSize) {
131 var element = elementDecoder.decode(byteSlice.slice(i, elementSize));
132 elements.add(element);
133 }
134 } else {
135 // An array with aligned elements and a vector of framing offsets in the end.
136 int framingOffsetSize = byteCount(byteSlice.limit());
137 int lastFramingOffset =
138 getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize));
139 int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize;
140
141 elements = new ArrayList<>(elementCount);
142 int position = 0;
143 for (int i = 0; i < elementCount; i++) {
144 int framingOffset =
145 getIntN(
146 byteSlice.slice(lastFramingOffset + i * framingOffsetSize, framingOffsetSize));
147 elements.add(
148 elementDecoder.decode(byteSlice.slice(position, framingOffset - position)));
149 position = align(framingOffset, alignment());
150 }
151 }
152
153 return new Array<>(elements);
154 }
155 };
156 }
157
158 private static int align(int offset, byte alignment) {
159 return offset % alignment == 0 ? offset : offset + alignment - (offset % alignment);
160 }
161
162 private static int getIntN(ByteBuffer byteSlice) {
163 var intBytes = new byte[4];
164 byteSlice.get(intBytes, 0, Math.min(4, byteSlice.limit()));
165 return ByteBuffer.wrap(intBytes).order(LITTLE_ENDIAN).getInt();
166 }
167
168 @SuppressWarnings("java:S3358")
169 private static int byteCount(int n) {
170 return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4;
171 }
172
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100173 /**
174 * Creates a {@link Decoder} for a {@link Maybe} type.
175 *
176 * @param elementDecoder a {@link Decoder} for the contained element.
177 * @param <U> the element type.
178 * @return a new {@link Decoder}.
179 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100180 public static <U extends Value> Decoder<Maybe<U>> ofMaybe(Decoder<U> elementDecoder) {
181 return new Decoder<>() {
182 @Override
183 public byte alignment() {
184 return elementDecoder.alignment();
185 }
186
187 @Override
188 @Nullable
189 Integer fixedSize() {
190 return null;
191 }
192
193 @Override
194 public Maybe<U> decode(ByteBuffer byteSlice) {
195 if (!byteSlice.hasRemaining()) {
196 return new Maybe<>(Optional.empty());
197 } else {
198 if (!elementDecoder.hasFixedSize()) {
199 // Remove trailing zero byte.
200 byteSlice.limit(byteSlice.limit() - 1);
201 }
202
203 return new Maybe<>(Optional.of(elementDecoder.decode(byteSlice)));
204 }
205 }
206 };
207 }
208
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100209 /**
210 * Creates a {@link Decoder} for a {@link Structure} type.
211 *
212 * @param recordType the {@link Record} type that represents the components of the structure.
213 * @param componentDecoders a {@link Decoder} for each component of the structure.
214 * @param <U> the {@link Record} type that represents the components of the structure.
215 * @return a new {@link Decoder}.
216 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100217 @SafeVarargs
218 public static <U extends Record> Decoder<Structure<U>> ofStructure(
219 Class<U> recordType, Decoder<? extends Value>... componentDecoders) {
220 var recordComponents = recordType.getRecordComponents();
221 if (componentDecoders.length != recordComponents.length) {
222 throw new IllegalArgumentException(
223 "number of decoders (%d) does not match number of structure components (%d)"
224 .formatted(componentDecoders.length, recordComponents.length));
225 }
226
227 return new Decoder<>() {
228 @Override
229 public byte alignment() {
230 return (byte) Arrays.stream(componentDecoders).mapToInt(Decoder::alignment).max().orElse(1);
231 }
232
233 @Override
234 public Integer fixedSize() {
235 int position = 0;
236 for (var componentDecoder : componentDecoders) {
237 var fixedComponentSize = componentDecoder.fixedSize();
238 if (fixedComponentSize == null) {
239 return null;
240 }
241
242 position = align(position, componentDecoder.alignment());
243 position += fixedComponentSize;
244 }
245
246 if (position == 0) {
247 return 1;
248 }
249
250 return align(position, alignment());
251 }
252
253 @Override
254 public Structure<U> decode(ByteBuffer byteSlice) {
255 int framingOffsetSize = byteCount(byteSlice.limit());
256
257 var recordConstructorArguments = new Object[recordComponents.length];
258
259 int position = 0;
260 int framingOffsetIndex = 0;
261 int componentIndex = 0;
262 for (var componentDecoder : componentDecoders) {
263 position = align(position, componentDecoder.alignment());
264
265 var fixedComponentSize = componentDecoder.fixedSize();
266 if (fixedComponentSize != null) {
267 recordConstructorArguments[componentIndex] =
268 componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
269 position += fixedComponentSize;
270 } else {
271 if (componentIndex == recordComponents.length - 1) {
272 // The last component never has a framing offset.
273 int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
274 recordConstructorArguments[componentIndex] =
275 componentDecoder.decode(byteSlice.slice(position, endPosition - position));
276 position = endPosition;
277 } else {
278 int framingOffset =
279 getIntN(
280 byteSlice.slice(
281 byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
282 framingOffsetSize));
283 recordConstructorArguments[componentIndex] =
284 componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
285 position = framingOffset;
286 ++framingOffsetIndex;
287 }
288 }
289
290 ++componentIndex;
291 }
292
293 try {
294 var recordComponentTypes =
295 Arrays.stream(recordType.getRecordComponents())
296 .map(RecordComponent::getType)
297 .toArray(Class<?>[]::new);
298 var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes);
299 return new Structure<>(recordConstructor.newInstance(recordConstructorArguments));
300 } catch (NoSuchMethodException
301 | InstantiationException
302 | IllegalAccessException
303 | InvocationTargetException e) {
304 throw new IllegalStateException(e);
305 }
306 }
307 };
308 }
309
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100310 /**
311 * Creates a {@link Decoder} for the {@link Variant} type.
312 *
313 * @return a new {@link Decoder}.
314 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100315 public static Decoder<Variant> ofVariant() {
316 return new Decoder<>() {
317 @Override
318 public byte alignment() {
319 return 8;
320 }
321
322 @Override
323 @Nullable
324 Integer fixedSize() {
325 return null;
326 }
327
328 @Override
329 public Variant decode(ByteBuffer byteSlice) {
330 // TODO
331 throw new UnsupportedOperationException("not implemented");
332 }
333 };
334 }
335
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100336 /**
337 * Creates a {@link Decoder} for the {@link Bool} type.
338 *
339 * @return a new {@link Decoder}.
340 */
341 public static Decoder<Bool> ofBool() {
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100342 return new Decoder<>() {
343 @Override
344 public byte alignment() {
345 return 1;
346 }
347
348 @Override
349 public Integer fixedSize() {
350 return 1;
351 }
352
353 @Override
354 public Bool decode(ByteBuffer byteSlice) {
355 return new Bool(byteSlice.get() != 0);
356 }
357 };
358 }
359
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100360 /**
361 * Creates a {@link Decoder} for the {@link Int8} type.
362 *
363 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
364 * result of this method.
365 *
366 * @return a new {@link Decoder}.
367 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100368 public static Decoder<Int8> ofInt8() {
369 return new Decoder<>() {
370 @Override
371 public byte alignment() {
372 return 1;
373 }
374
375 @Override
376 public Integer fixedSize() {
377 return 1;
378 }
379
380 @Override
381 public Int8 decode(ByteBuffer byteSlice) {
382 return new Int8(byteSlice.get());
383 }
384 };
385 }
386
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100387 /**
388 * Creates a {@link Decoder} for the {@link Int16} type.
389 *
390 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
391 * result of this method.
392 *
393 * @return a new {@link Decoder}.
394 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100395 public static Decoder<Int16> ofInt16() {
396 return new Decoder<>() {
397 @Override
398 public byte alignment() {
399 return 2;
400 }
401
402 @Override
403 public Integer fixedSize() {
404 return 2;
405 }
406
407 @Override
408 public Int16 decode(ByteBuffer byteSlice) {
409 return new Int16(byteSlice.getShort());
410 }
411 };
412 }
413
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100414 /**
415 * Creates a {@link Decoder} for the {@link Int32} type.
416 *
417 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
418 * result of this method.
419 *
420 * @return a new {@link Decoder}.
421 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100422 public static Decoder<Int32> ofInt32() {
423 return new Decoder<>() {
424 @Override
425 public byte alignment() {
426 return 4;
427 }
428
429 @Override
430 public Integer fixedSize() {
431 return 4;
432 }
433
434 @Override
435 public Int32 decode(ByteBuffer byteSlice) {
436 return new Int32(byteSlice.getInt());
437 }
438 };
439 }
440
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100441 /**
442 * Creates a {@link Decoder} for the {@link Int64} type.
443 *
444 * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
445 * result of this method.
446 *
447 * @return a new {@link Decoder}.
448 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100449 public static Decoder<Int64> ofInt64() {
450 return new Decoder<>() {
451 @Override
452 public byte alignment() {
453 return 8;
454 }
455
456 @Override
457 public Integer fixedSize() {
458 return 8;
459 }
460
461 @Override
462 public Int64 decode(ByteBuffer byteSlice) {
463 return new Int64(byteSlice.getLong());
464 }
465 };
466 }
467
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100468 /**
469 * Creates a {@link Decoder} for the {@link Float64} type.
470 *
471 * @return a new {@link Decoder}.
472 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100473 public static Decoder<Float64> ofFloat64() {
474 return new Decoder<>() {
475 @Override
476 public byte alignment() {
477 return 8;
478 }
479
480 @Override
481 public Integer fixedSize() {
482 return 8;
483 }
484
485 @Override
486 public Float64 decode(ByteBuffer byteSlice) {
487 return new Float64(byteSlice.getDouble());
488 }
489 };
490 }
491
Matthias Andreas Benkard4c32c392021-12-12 21:23:53 +0100492 /**
493 * Creates a {@link Decoder} for the {@link Str} type.
494 *
495 * <p><strong>Note:</strong> While GVariant does not prescribe any particular encoding, {@link
496 * java.nio.charset.StandardCharsets#UTF_8} is the most common choice.
497 *
498 * @return a new {@link Decoder}.
499 */
Matthias Andreas Benkard261532a2021-12-12 20:09:27 +0100500 public static Decoder<Str> ofStr(Charset charset) {
501 return new Decoder<>() {
502 @Override
503 public byte alignment() {
504 return 1;
505 }
506
507 @Override
508 @Nullable
509 Integer fixedSize() {
510 return null;
511 }
512
513 @Override
514 public Str decode(ByteBuffer byteSlice) {
515 byteSlice.limit(byteSlice.limit() - 1);
516 return new Str(charset.decode(byteSlice).toString());
517 }
518 };
519 }
520}