blob: 2ebd0afbdb27ffd743f59fca1205f3948cdc4d63 [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 *
33 * @param <T> the type that the {@link Decoder} can decode.
34 */
35@SuppressWarnings("java:S1610")
36public abstract class Decoder<T extends Value> {
37
38 private Decoder() {}
39
40 /**
41 * @throws java.nio.BufferUnderflowException if the byte buffer is shorter than the requested
42 * data.
43 */
44 public abstract T decode(ByteBuffer byteSlice);
45
46 abstract byte alignment();
47
48 @Nullable
49 abstract Integer fixedSize();
50
51 final boolean hasFixedSize() {
52 return fixedSize() != null;
53 }
54
55 public Decoder<T> withByteOrder(ByteOrder byteOrder) {
56 var delegate = this;
57
58 return new Decoder<>() {
59 @Override
60 public byte alignment() {
61 return delegate.alignment();
62 }
63
64 @Override
65 public @Nullable Integer fixedSize() {
66 return delegate.fixedSize();
67 }
68
69 @Override
70 public T decode(ByteBuffer byteSlice) {
71 byteSlice.order(byteOrder);
72 return delegate.decode(byteSlice);
73 }
74 };
75 }
76
77 public static <U extends Value> Decoder<Array<U>> ofArray(Decoder<U> elementDecoder) {
78 return new Decoder<>() {
79 @Override
80 public byte alignment() {
81 return elementDecoder.alignment();
82 }
83
84 @Override
85 @Nullable
86 Integer fixedSize() {
87 return null;
88 }
89
90 @Override
91 public Array<U> decode(ByteBuffer byteSlice) {
92 List<U> elements;
93
94 var elementSize = elementDecoder.fixedSize();
95 if (elementSize != null) {
96 // A simple C-style array.
97 elements = new ArrayList<>(byteSlice.limit() / elementSize);
98 for (int i = 0; i < byteSlice.limit(); i += elementSize) {
99 var element = elementDecoder.decode(byteSlice.slice(i, elementSize));
100 elements.add(element);
101 }
102 } else {
103 // An array with aligned elements and a vector of framing offsets in the end.
104 int framingOffsetSize = byteCount(byteSlice.limit());
105 int lastFramingOffset =
106 getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize));
107 int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize;
108
109 elements = new ArrayList<>(elementCount);
110 int position = 0;
111 for (int i = 0; i < elementCount; i++) {
112 int framingOffset =
113 getIntN(
114 byteSlice.slice(lastFramingOffset + i * framingOffsetSize, framingOffsetSize));
115 elements.add(
116 elementDecoder.decode(byteSlice.slice(position, framingOffset - position)));
117 position = align(framingOffset, alignment());
118 }
119 }
120
121 return new Array<>(elements);
122 }
123 };
124 }
125
126 private static int align(int offset, byte alignment) {
127 return offset % alignment == 0 ? offset : offset + alignment - (offset % alignment);
128 }
129
130 private static int getIntN(ByteBuffer byteSlice) {
131 var intBytes = new byte[4];
132 byteSlice.get(intBytes, 0, Math.min(4, byteSlice.limit()));
133 return ByteBuffer.wrap(intBytes).order(LITTLE_ENDIAN).getInt();
134 }
135
136 @SuppressWarnings("java:S3358")
137 private static int byteCount(int n) {
138 return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4;
139 }
140
141 public static <U extends Value> Decoder<Maybe<U>> ofMaybe(Decoder<U> elementDecoder) {
142 return new Decoder<>() {
143 @Override
144 public byte alignment() {
145 return elementDecoder.alignment();
146 }
147
148 @Override
149 @Nullable
150 Integer fixedSize() {
151 return null;
152 }
153
154 @Override
155 public Maybe<U> decode(ByteBuffer byteSlice) {
156 if (!byteSlice.hasRemaining()) {
157 return new Maybe<>(Optional.empty());
158 } else {
159 if (!elementDecoder.hasFixedSize()) {
160 // Remove trailing zero byte.
161 byteSlice.limit(byteSlice.limit() - 1);
162 }
163
164 return new Maybe<>(Optional.of(elementDecoder.decode(byteSlice)));
165 }
166 }
167 };
168 }
169
170 @SafeVarargs
171 public static <U extends Record> Decoder<Structure<U>> ofStructure(
172 Class<U> recordType, Decoder<? extends Value>... componentDecoders) {
173 var recordComponents = recordType.getRecordComponents();
174 if (componentDecoders.length != recordComponents.length) {
175 throw new IllegalArgumentException(
176 "number of decoders (%d) does not match number of structure components (%d)"
177 .formatted(componentDecoders.length, recordComponents.length));
178 }
179
180 return new Decoder<>() {
181 @Override
182 public byte alignment() {
183 return (byte) Arrays.stream(componentDecoders).mapToInt(Decoder::alignment).max().orElse(1);
184 }
185
186 @Override
187 public Integer fixedSize() {
188 int position = 0;
189 for (var componentDecoder : componentDecoders) {
190 var fixedComponentSize = componentDecoder.fixedSize();
191 if (fixedComponentSize == null) {
192 return null;
193 }
194
195 position = align(position, componentDecoder.alignment());
196 position += fixedComponentSize;
197 }
198
199 if (position == 0) {
200 return 1;
201 }
202
203 return align(position, alignment());
204 }
205
206 @Override
207 public Structure<U> decode(ByteBuffer byteSlice) {
208 int framingOffsetSize = byteCount(byteSlice.limit());
209
210 var recordConstructorArguments = new Object[recordComponents.length];
211
212 int position = 0;
213 int framingOffsetIndex = 0;
214 int componentIndex = 0;
215 for (var componentDecoder : componentDecoders) {
216 position = align(position, componentDecoder.alignment());
217
218 var fixedComponentSize = componentDecoder.fixedSize();
219 if (fixedComponentSize != null) {
220 recordConstructorArguments[componentIndex] =
221 componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
222 position += fixedComponentSize;
223 } else {
224 if (componentIndex == recordComponents.length - 1) {
225 // The last component never has a framing offset.
226 int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
227 recordConstructorArguments[componentIndex] =
228 componentDecoder.decode(byteSlice.slice(position, endPosition - position));
229 position = endPosition;
230 } else {
231 int framingOffset =
232 getIntN(
233 byteSlice.slice(
234 byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
235 framingOffsetSize));
236 recordConstructorArguments[componentIndex] =
237 componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
238 position = framingOffset;
239 ++framingOffsetIndex;
240 }
241 }
242
243 ++componentIndex;
244 }
245
246 try {
247 var recordComponentTypes =
248 Arrays.stream(recordType.getRecordComponents())
249 .map(RecordComponent::getType)
250 .toArray(Class<?>[]::new);
251 var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes);
252 return new Structure<>(recordConstructor.newInstance(recordConstructorArguments));
253 } catch (NoSuchMethodException
254 | InstantiationException
255 | IllegalAccessException
256 | InvocationTargetException e) {
257 throw new IllegalStateException(e);
258 }
259 }
260 };
261 }
262
263 public static Decoder<Variant> ofVariant() {
264 return new Decoder<>() {
265 @Override
266 public byte alignment() {
267 return 8;
268 }
269
270 @Override
271 @Nullable
272 Integer fixedSize() {
273 return null;
274 }
275
276 @Override
277 public Variant decode(ByteBuffer byteSlice) {
278 // TODO
279 throw new UnsupportedOperationException("not implemented");
280 }
281 };
282 }
283
284 public static Decoder<Bool> ofBoolean() {
285 return new Decoder<>() {
286 @Override
287 public byte alignment() {
288 return 1;
289 }
290
291 @Override
292 public Integer fixedSize() {
293 return 1;
294 }
295
296 @Override
297 public Bool decode(ByteBuffer byteSlice) {
298 return new Bool(byteSlice.get() != 0);
299 }
300 };
301 }
302
303 public static Decoder<Int8> ofInt8() {
304 return new Decoder<>() {
305 @Override
306 public byte alignment() {
307 return 1;
308 }
309
310 @Override
311 public Integer fixedSize() {
312 return 1;
313 }
314
315 @Override
316 public Int8 decode(ByteBuffer byteSlice) {
317 return new Int8(byteSlice.get());
318 }
319 };
320 }
321
322 public static Decoder<Int16> ofInt16() {
323 return new Decoder<>() {
324 @Override
325 public byte alignment() {
326 return 2;
327 }
328
329 @Override
330 public Integer fixedSize() {
331 return 2;
332 }
333
334 @Override
335 public Int16 decode(ByteBuffer byteSlice) {
336 return new Int16(byteSlice.getShort());
337 }
338 };
339 }
340
341 public static Decoder<Int32> ofInt32() {
342 return new Decoder<>() {
343 @Override
344 public byte alignment() {
345 return 4;
346 }
347
348 @Override
349 public Integer fixedSize() {
350 return 4;
351 }
352
353 @Override
354 public Int32 decode(ByteBuffer byteSlice) {
355 return new Int32(byteSlice.getInt());
356 }
357 };
358 }
359
360 public static Decoder<Int64> ofInt64() {
361 return new Decoder<>() {
362 @Override
363 public byte alignment() {
364 return 8;
365 }
366
367 @Override
368 public Integer fixedSize() {
369 return 8;
370 }
371
372 @Override
373 public Int64 decode(ByteBuffer byteSlice) {
374 return new Int64(byteSlice.getLong());
375 }
376 };
377 }
378
379 public static Decoder<Float64> ofFloat64() {
380 return new Decoder<>() {
381 @Override
382 public byte alignment() {
383 return 8;
384 }
385
386 @Override
387 public Integer fixedSize() {
388 return 8;
389 }
390
391 @Override
392 public Float64 decode(ByteBuffer byteSlice) {
393 return new Float64(byteSlice.getDouble());
394 }
395 };
396 }
397
398 public static Decoder<Str> ofStr(Charset charset) {
399 return new Decoder<>() {
400 @Override
401 public byte alignment() {
402 return 1;
403 }
404
405 @Override
406 @Nullable
407 Integer fixedSize() {
408 return null;
409 }
410
411 @Override
412 public Str decode(ByteBuffer byteSlice) {
413 byteSlice.limit(byteSlice.limit() - 1);
414 return new Str(charset.decode(byteSlice).toString());
415 }
416 };
417 }
418}