1 package org.djutils.serialization;
2
3 import java.io.IOException;
4
5 import org.djunits.unit.Unit;
6 import org.djunits.value.vdouble.scalar.SIScalar;
7 import org.djunits.value.vdouble.scalar.base.DoubleScalar;
8 import org.djunits.value.vfloat.scalar.FloatSIScalar;
9 import org.djunits.value.vfloat.scalar.base.FloatScalar;
10 import org.djutils.decoderdumper.Decoder;
11 import org.djutils.logger.CategoryLogger;
12 import org.djutils.serialization.serializers.ArrayOrMatrixWithUnitSerializer;
13 import org.djutils.serialization.serializers.BasicPrimitiveArrayOrMatrixSerializer;
14 import org.djutils.serialization.serializers.FixedSizeObjectSerializer;
15 import org.djutils.serialization.serializers.Pointer;
16 import org.djutils.serialization.serializers.Serializer;
17 import org.djutils.serialization.serializers.StringArraySerializer;
18 import org.djutils.serialization.serializers.StringMatrixSerializer;
19
20
21
22
23
24
25
26
27
28
29
30 public class SerialDataDecoder implements Decoder
31 {
32
33 private final Endianness endianness;
34
35
36 private byte currentFieldType;
37
38
39 private Serializer<?> currentSerializer = null;
40
41
42 private int nextDataElementByte = -1;
43
44
45 private byte[] dataElementBytes = new byte[0];
46
47
48 private int rowCount;
49
50
51 private int columnCount;
52
53
54 private int charCount;
55
56
57 private int currentRow;
58
59
60 private int currentColumn;
61
62
63 private int currentChar;
64
65
66 private Unit<?> displayUnit;
67
68
69 private Unit<?>[] columnUnits = null;
70
71
72 private StringBuilder buffer = new StringBuilder();
73
74
75
76
77
78 public SerialDataDecoder(final Endianness endianness)
79 {
80 this.endianness = endianness;
81 }
82
83 @Override
84 public final String getResult()
85 {
86 String result = this.buffer.toString();
87 this.buffer.setLength(0);
88 return result;
89 }
90
91 @Override
92 public final int getMaximumWidth()
93 {
94 return 80;
95 }
96
97
98
99
100
101
102
103
104
105
106 @Override
107 public final boolean append(final int address, final byte theByte) throws IOException
108 {
109 boolean result = false;
110
111
112 if (this.currentSerializer == null)
113 {
114 result = processFieldTypeByte(theByte);
115 return result;
116 }
117
118
119 if (this.nextDataElementByte < this.dataElementBytes.length)
120 {
121 this.dataElementBytes[this.nextDataElementByte] = theByte;
122 }
123 this.nextDataElementByte++;
124
125 if (this.nextDataElementByte == this.dataElementBytes.length)
126 {
127 result = processDataElement();
128 }
129
130
131 if (this.currentSerializer == null)
132 {
133 return true;
134 }
135 return result;
136 }
137
138
139
140
141
142
143 private boolean processFieldTypeByte(final byte fieldType)
144 {
145 this.currentFieldType = fieldType;
146 this.currentSerializer = TypedObject.PRIMITIVE_DATA_DECODERS.get(this.currentFieldType);
147 if (this.currentSerializer == null)
148 {
149 this.buffer.append(String.format("Error: Bad field type %02x - resynchronizing", this.currentFieldType));
150 return true;
151 }
152 this.buffer.append(this.currentSerializer.dataClassName() + (this.currentSerializer.getNumberOfDimensions() > 0
153 || this.currentSerializer.dataClassName().startsWith("Djunits") ? " " : ": "));
154
155 this.columnCount = 0;
156 this.rowCount = 0;
157 this.displayUnit = null;
158 this.columnUnits = null;
159
160
161 if (this.currentSerializer instanceof FixedSizeObjectSerializer<?>)
162 {
163 var fsoe = (FixedSizeObjectSerializer<?>) this.currentSerializer;
164 int size = fsoe.size(null);
165 prepareForDataElement(size);
166 return false;
167 }
168
169
170 if (this.currentSerializer.getNumberOfDimensions() > 0)
171 {
172 int size = this.currentSerializer.getNumberOfDimensions() * 4;
173 prepareForDataElement(size);
174 return false;
175 }
176
177
178 if (this.currentFieldType == 9 || this.currentFieldType == 10)
179 {
180 prepareForDataElement(4);
181 return false;
182 }
183
184
185 if (this.currentFieldType == 25 || this.currentFieldType == 26)
186 {
187 prepareForDataElement(2);
188 return false;
189 }
190
191 this.buffer
192 .append(String.format("Error: No field type handler for type %02x - resynchronizing", this.currentFieldType));
193 return true;
194 }
195
196
197
198
199
200 private boolean processDataElement()
201 {
202 boolean result = false;
203
204
205 if (this.currentSerializer instanceof FixedSizeObjectSerializer<?>)
206 {
207 result = appendFixedSizeObject();
208 done();
209 return result;
210 }
211
212
213 if (this.currentFieldType == 9 || this.currentFieldType == 10)
214 {
215 appendString();
216 if (this.currentChar >= this.charCount)
217 {
218 done();
219 }
220 return false;
221 }
222
223
224 if (this.currentFieldType == 31 || this.currentFieldType == 32)
225 {
226 if (this.rowCount == 0)
227 {
228 processRowsCols();
229 prepareForDataElement(2 * this.columnCount);
230 return false;
231 }
232 if (this.columnUnits == null)
233 {
234 return fillDjunitsVectorArrayColumnUnits();
235 }
236 return appendDjunitsVectorArrayElement();
237 }
238
239
240 if (this.currentSerializer.getNumberOfDimensions() > 0)
241 {
242 if (this.rowCount == 0)
243 {
244 processRowsCols();
245 if (this.currentSerializer.hasUnit())
246 {
247 prepareForDataElement(2);
248 }
249 else if (this.currentSerializer instanceof BasicPrimitiveArrayOrMatrixSerializer<?>)
250 {
251 var bpams = (BasicPrimitiveArrayOrMatrixSerializer<?>) this.currentSerializer;
252 prepareForDataElement(bpams.getElementSize());
253 }
254 else if (this.currentSerializer instanceof StringArraySerializer
255 || this.currentSerializer instanceof StringMatrixSerializer)
256 {
257 prepareForDataElement(4);
258 }
259 return false;
260 }
261 if (this.currentSerializer.hasUnit())
262 {
263 if (this.displayUnit == null)
264 {
265 result = processUnit();
266 prepareForDataElement(((ArrayOrMatrixWithUnitSerializer<?, ?>) this.currentSerializer).getElementSize());
267 return result;
268 }
269 result = appendDjunitsElement();
270 prepareForDataElement(this.dataElementBytes.length);
271 incColumnCount();
272 return result;
273 }
274 if (this.currentSerializer instanceof StringArraySerializer
275 || this.currentSerializer instanceof StringMatrixSerializer)
276 {
277 processStringElement();
278 return false;
279 }
280 result = appendPrimitiveElement();
281 prepareForDataElement(this.dataElementBytes.length);
282 incColumnCount();
283 return result;
284 }
285
286
287 if (this.currentFieldType == 25 || this.currentFieldType == 26)
288 {
289 if (this.displayUnit == null)
290 {
291 result = processUnit();
292 prepareForDataElement(getSize() - 2);
293 return result;
294 }
295 result = appendDjunitsElement();
296 done();
297 return result;
298 }
299
300
301 CategoryLogger.always().warn("Did not process type {}", this.currentFieldType);
302 return true;
303 }
304
305
306
307
308
309 private int getSize()
310 {
311 try
312 {
313 return this.currentSerializer.size(null);
314 }
315 catch (SerializationException e)
316 {
317 CategoryLogger.always().error(e, "Could not determine size of element for field type {}", this.currentFieldType);
318 return 1;
319 }
320 }
321
322
323
324
325
326 private boolean appendFixedSizeObject()
327 {
328 try
329 {
330 Object value = this.currentSerializer.deSerialize(this.dataElementBytes, new Pointer(), this.endianness);
331 this.buffer.append(value.toString());
332 }
333 catch (SerializationException e)
334 {
335 this.buffer.append("Error deserializing data");
336 return true;
337 }
338 return false;
339 }
340
341
342
343
344 private void appendString()
345 {
346 int elementSize = this.currentSerializer.dataClassName().contains("8") ? 1 : 2;
347 if (this.charCount == 0)
348 {
349 this.charCount = this.endianness.decodeInt(this.dataElementBytes, 0);
350 this.currentChar = 0;
351 prepareForDataElement(elementSize);
352 }
353 else
354 {
355 if (elementSize == 1)
356 {
357 if (this.dataElementBytes[0] > 32 && this.dataElementBytes[0] < 127)
358 {
359 this.buffer.append((char) this.dataElementBytes[0]);
360 }
361 else
362 {
363 this.buffer.append(".");
364 }
365 }
366 else
367 {
368 char character = this.endianness.decodeChar(this.dataElementBytes, 0);
369 if (Character.isAlphabetic(character))
370 {
371 this.buffer.append(character);
372 }
373 else
374 {
375 this.buffer.append(".");
376 }
377 }
378 this.currentChar++;
379 }
380 this.nextDataElementByte = 0;
381 }
382
383
384
385
386 private void processStringElement()
387 {
388 appendString();
389 if (this.currentChar >= this.charCount)
390 {
391 incColumnCount();
392 this.charCount = 0;
393 prepareForDataElement(4);
394 }
395 }
396
397
398
399
400 private void processRowsCols()
401 {
402 this.currentRow = 0;
403 this.currentColumn = 0;
404 if (this.dataElementBytes.length == 8)
405 {
406 this.rowCount = this.endianness.decodeInt(this.dataElementBytes, 0);
407 this.columnCount = this.endianness.decodeInt(this.dataElementBytes, 4);
408 this.buffer.append(String.format("height %d, width %d: ", this.rowCount, this.columnCount));
409 }
410 else
411 {
412 this.columnCount = this.endianness.decodeInt(this.dataElementBytes, 0);
413 this.rowCount = 1;
414 this.buffer.append(String.format("length %d: ", this.columnCount));
415 }
416 }
417
418
419
420
421
422 private boolean fillDjunitsVectorArrayColumnUnits()
423 {
424 boolean result = false;
425 this.columnUnits = new Unit<?>[this.columnCount];
426 for (int i = 0; i < this.columnCount; i++)
427 {
428 byte unitTypeCode = this.dataElementBytes[2 * i];
429 byte displayUnitCode = this.dataElementBytes[2 * i + 1];
430 this.columnUnits[i] = UnitType.getUnit(unitTypeCode, displayUnitCode);
431 if (this.columnUnits[i] == null && !result)
432 {
433 this.buffer.append(
434 String.format("Error: Could not find unit type %d, display unit %d", unitTypeCode, displayUnitCode));
435 result = true;
436 }
437 }
438 prepareForDataElement(this.currentFieldType == 31 ? 4 : 8);
439 return result;
440 }
441
442
443
444
445
446
447
448
449 @SuppressWarnings("unchecked")
450 private <U extends Unit<U>, FS extends FloatScalar<U, FS>,
451 DS extends DoubleScalar<U, DS>> boolean appendDjunitsVectorArrayElement()
452 {
453 boolean result = false;
454 try
455 {
456 U unit = (U) this.columnUnits[this.currentColumn];
457 if (this.currentFieldType == 31)
458 {
459 float f = this.endianness.decodeFloat(this.dataElementBytes, 0);
460 FloatScalar<U, FS> afs = FloatSIScalar.instantiateAnonymous(f, unit.getStandardUnit());
461 afs.setDisplayUnit(unit);
462 this.buffer.append(afs.toDisplayString().replace(" ", "") + " ");
463 }
464 else
465 {
466 double d = this.endianness.decodeDouble(this.dataElementBytes, 0);
467 DoubleScalar<U, DS> ads = SIScalar.instantiateAnonymous(d, unit.getStandardUnit());
468 ads.setDisplayUnit(unit);
469 this.buffer.append(ads.toDisplayString().replace(" ", "") + " ");
470 }
471 }
472 catch (Exception e)
473 {
474 this.buffer.append("Error: Illegal element in vector array -- could not parse");
475 result = true;
476 }
477 prepareForDataElement(this.dataElementBytes.length);
478 incColumnCount();
479 return result;
480 }
481
482
483
484
485
486 private boolean processUnit()
487 {
488 byte unitTypeCode = this.dataElementBytes[0];
489 byte displayUnitCode = this.dataElementBytes[1];
490 this.displayUnit = UnitType.getUnit(unitTypeCode, displayUnitCode);
491 if (this.displayUnit == null)
492 {
493 this.buffer
494 .append(String.format("Error: Could not find unit ype %d, display unit %d", unitTypeCode, displayUnitCode));
495 return true;
496 }
497 return false;
498 }
499
500
501
502
503
504
505
506
507 @SuppressWarnings("unchecked")
508 private <U extends Unit<U>, FS extends FloatScalar<U, FS>, DS extends DoubleScalar<U, DS>> boolean appendDjunitsElement()
509 {
510 boolean result = false;
511 try
512 {
513 if (this.dataElementBytes.length == 4)
514 {
515 float f = this.endianness.decodeFloat(this.dataElementBytes, 0);
516 FloatScalar<U, FS> afs = FloatSIScalar.instantiateAnonymous(f, this.displayUnit.getStandardUnit());
517 afs.setDisplayUnit((U) this.displayUnit);
518 this.buffer.append(afs.toDisplayString().replace(" ", "") + " ");
519 }
520 else
521 {
522 double d = this.endianness.decodeDouble(this.dataElementBytes, 0);
523 DoubleScalar<U, DS> ads = SIScalar.instantiateAnonymous(d, this.displayUnit.getStandardUnit());
524 ads.setDisplayUnit((U) this.displayUnit);
525 this.buffer.append(ads.toDisplayString().replace(" ", "") + " ");
526 }
527 }
528 catch (Exception e)
529 {
530 this.buffer.append("Error: Could not instantiate djunits element");
531 result = true;
532 }
533 return result;
534 }
535
536
537
538
539
540 private boolean appendPrimitiveElement()
541 {
542 boolean result = false;
543 this.buffer.append(switch (this.currentSerializer.fieldType())
544 {
545
546 case FieldTypes.BYTE_8_ARRAY, FieldTypes.BYTE_8_MATRIX ->
547 String.format("%02x ", this.dataElementBytes[0]);
548 case FieldTypes.SHORT_16_ARRAY, FieldTypes.SHORT_16_MATRIX ->
549 String.format("%d ", this.endianness.decodeShort(this.dataElementBytes, 0));
550 case FieldTypes.INT_32_ARRAY, FieldTypes.INT_32_MATRIX ->
551 String.format("%d ", this.endianness.decodeInt(this.dataElementBytes, 0));
552 case FieldTypes.LONG_64_ARRAY, FieldTypes.LONG_64_MATRIX ->
553 String.format("%d ", this.endianness.decodeLong(this.dataElementBytes, 0));
554 case FieldTypes.FLOAT_32_ARRAY, FieldTypes.FLOAT_32_MATRIX ->
555 String.format("%f ", this.endianness.decodeFloat(this.dataElementBytes, 0));
556 case FieldTypes.DOUBLE_64_ARRAY, FieldTypes.DOUBLE_64_MATRIX ->
557 String.format("%f ", this.endianness.decodeDouble(this.dataElementBytes, 0));
558 case FieldTypes.BOOLEAN_8_ARRAY, FieldTypes.BOOLEAN_8_MATRIX ->
559 this.dataElementBytes[0] == 0 ? "false " : "true ";
560
561 default -> "Error: Unhandled type of basicPrimitiveArraySerializer: " + this.currentSerializer.fieldType();
562 });
563 return result;
564 }
565
566
567
568
569 private void incColumnCount()
570 {
571 this.currentColumn++;
572 if (this.currentColumn >= this.columnCount)
573 {
574 this.currentColumn = 0;
575 this.currentRow++;
576 if (this.currentRow >= this.rowCount)
577 {
578 done();
579 }
580 }
581 }
582
583
584
585
586 private void done()
587 {
588 this.currentSerializer = null;
589 this.rowCount = 0;
590 this.columnCount = 0;
591 this.charCount = 0;
592 }
593
594
595
596
597
598 private void prepareForDataElement(final int dataElementSize)
599 {
600 this.dataElementBytes = new byte[dataElementSize];
601 this.nextDataElementByte = 0;
602 }
603
604 @Override
605 public final boolean ignoreForIdenticalOutputCheck()
606 {
607 return false;
608 }
609
610 }