View Javadoc
1   package org.djutils.serialization;
2   
3   import java.io.IOException;
4   
5   import org.djunits.unit.Unit;
6   import org.djutils.decoderdumper.Decoder;
7   import org.djutils.serialization.serializers.ArrayOrMatrixSerializer;
8   import org.djutils.serialization.serializers.BasicPrimitiveArrayOrMatrixSerializer;
9   import org.djutils.serialization.serializers.FixedSizeObjectSerializer;
10  import org.djutils.serialization.serializers.ObjectSerializer;
11  import org.djutils.serialization.serializers.Pointer;
12  import org.djutils.serialization.serializers.Serializer;
13  
14  /**
15   * Decoder for inspection of serialized data.
16   * <p>
17   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
19   * <p>
20   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jan 3, 2019 <br>
21   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
23   */
24  public class SerialDataDecoder implements Decoder
25  {
26      /** The endian util to use to decode multi-byte values. */
27      private final EndianUtil endianUtil;
28  
29      /** Type of the data that is currently being decoded. */
30      private byte currentFieldType;
31  
32      /** The serializer for the <code>currentFieldType</code>. */
33      private Serializer<?> currentSerializer = null;
34  
35      /** Position in the data of the <code>dataElementBytes</code>. */
36      private int positionInData = -1;
37  
38      /** Position in the dataElementBytes where the next input byte shall be store. */
39      private int nextDataElementByte = -1;
40  
41      /** Collects the bytes that constitute the current data element. */
42      private byte[] dataElementBytes = new byte[0];
43  
44      /** Size of the data that is currently being decode. */
45      private int totalDataSize = -1;
46  
47      /** Number of rows in an array or matrix. */
48      private int rowCount;
49  
50      /** Number of columns in a matrix. */
51      private int columnCount;
52  
53      /** Row of matrix or array that we are now reading. */
54      private int currentRow;
55  
56      /** Column of matrix that we are now reading. */
57      private int currentColumn;
58  
59      /** Djunits display unit. */
60      private Unit<?> displayUnit;
61  
62      /** String builder for current output line. */
63      private StringBuilder buffer = new StringBuilder();
64  
65      /**
66       * Construct a new SerialDataDecoder.
67       * @param endianUtil EndianUtil; the endian util to use to decode multi-byte values
68       */
69      public SerialDataDecoder(final EndianUtil endianUtil)
70      {
71          this.endianUtil = endianUtil;
72      }
73  
74      /** {@inheritDoc} */
75      @Override
76      public final String getResult()
77      {
78          String result = this.buffer.toString();
79          this.buffer.setLength(0);
80          return result;
81      }
82  
83      /** {@inheritDoc} */
84      @Override
85      public final int getMaximumWidth()
86      {
87          return 80;
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public final boolean append(final int address, final byte theByte) throws IOException
93      {
94          boolean result = false;
95          if (null == this.currentSerializer)
96          {
97              // We are expecting a field type byte
98              this.currentFieldType = theByte;
99              this.currentSerializer = TypedMessage.PRIMITIVE_DATA_DECODERS.get(this.currentFieldType);
100             if (null == this.currentSerializer)
101             {
102                 this.buffer.append(String.format("Bad field type %02x - resynchronizing", this.currentFieldType));
103                 result = true;
104                 // May eventually re-synchronize, but that could take a lot of data.
105             }
106             else
107             {
108                 this.buffer.append(this.currentSerializer.dataClassName() + (this.currentSerializer.getNumberOfDimensions() > 0
109                         || this.currentSerializer.dataClassName().startsWith("Djunits") ? " " : ": "));
110                 this.positionInData = 1;
111                 this.totalDataSize = 1; // to be adjusted
112                 this.columnCount = 0;
113                 this.rowCount = 0;
114                 this.displayUnit = null;
115                 if (this.currentSerializer.dataClassName().startsWith("String_"))
116                 {
117                     prepareForDataElement(4);
118                     this.totalDataSize += 4;
119                 }
120                 else if (this.currentSerializer.dataClassName().contentEquals("Djunits_vector_array"))
121                 {
122                     prepareForDataElement(8);
123                     this.totalDataSize += 8;
124                 }
125                 else if (this.currentSerializer.getNumberOfDimensions() > 0)
126                 {
127                     int size = this.currentSerializer.getNumberOfDimensions() * 4;
128                     prepareForDataElement(size);
129                     this.totalDataSize += size;
130                 }
131                 else if (this.currentSerializer instanceof ObjectSerializer)
132                 {
133                     try
134                     {
135                         int size;
136                         if (this.currentSerializer.dataClassName().startsWith("Djunits"))
137                         {
138                             // We won't get away calling the size method with null here
139                             // Prepare to get the display unit; requires at least two more bytes
140                             size = 2;
141                             this.displayUnit = null;
142                         }
143                         else
144                         {
145                             size = this.currentSerializer.size(null);
146                         }
147                         prepareForDataElement(size);
148                         this.totalDataSize += size;
149                     }
150                     catch (SerializationException e)
151                     {
152                         e.printStackTrace();
153                     }
154                 }
155                 else
156                 {
157                     try
158                     {
159                         int size = this.currentSerializer.size(null);
160                         this.totalDataSize += size;
161                         prepareForDataElement(size);
162                     }
163                     catch (SerializationException e)
164                     {
165                         e.printStackTrace(); // Cannot happen
166                     }
167                 }
168             }
169             return result;
170         }
171         if (this.nextDataElementByte < this.dataElementBytes.length)
172         {
173             this.dataElementBytes[this.nextDataElementByte] = theByte;
174         }
175         this.nextDataElementByte++;
176         this.positionInData++;
177         if (this.nextDataElementByte == this.dataElementBytes.length)
178         {
179             if (this.currentSerializer.dataClassName().startsWith("String_"))
180             {
181                 int elementSize = this.currentSerializer.dataClassName().endsWith("8") ? 1 : 2;
182                 if (this.columnCount == 0) // re-using columnCount to store number of characters
183                 {
184                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
185                     prepareForDataElement(elementSize);
186                     this.totalDataSize += this.columnCount * elementSize;
187                 }
188                 else
189                 {
190                     if (1 == elementSize)
191                     {
192                         if (this.dataElementBytes[0] > 32 && this.dataElementBytes[0] < 127)
193                         {
194                             this.buffer.append((char) this.dataElementBytes[0]); // safe to print
195                         }
196                         else
197                         {
198                             this.buffer.append("."); // not safe to print
199                         }
200                     }
201                     else
202                     {
203                         char character = this.endianUtil.decodeChar(this.dataElementBytes, 0);
204                         if (Character.isAlphabetic(character))
205                         {
206                             this.buffer.append(character); // safe to print
207                         }
208                         else
209                         {
210                             this.buffer.append("."); // not safe to print
211                         }
212                     }
213                 }
214                 this.currentColumn = 0;
215                 this.nextDataElementByte = 0;
216             }
217             else if (this.currentSerializer.dataClassName().contentEquals("Djunits_vector_array"))
218             {
219                 if (this.rowCount == 0)
220                 {
221                     this.rowCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
222                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
223                     this.currentRow = -1; // indicates we are parsing the units
224                     this.currentColumn = 0;
225                     prepareForDataElement(2);
226                     this.totalDataSize += 2;
227                 }
228                 else if (this.currentRow < 0)
229                 {
230                     // parse one unit
231                     TypedMessage.getUnit(this.dataElementBytes, new Pointer(), this.endianUtil);
232                     this.displayUnit = TypedMessage.getUnit(this.dataElementBytes, new Pointer(), this.endianUtil);
233                     this.buffer.append("unit for column " + this.currentColumn + ": ");
234                     this.buffer.append(this.displayUnit);
235                     this.currentColumn++;
236                     if (this.currentColumn < this.columnCount)
237                     {
238                         prepareForDataElement(2);
239                         this.totalDataSize += 2;
240                         this.buffer.append(", ");
241                     }
242                     else
243                     {
244                         // Done with the units; prepare to parse the values
245                         this.currentRow = 0;
246                         this.currentColumn = 0;
247                         prepareForDataElement(8);
248                         this.totalDataSize += 8 * this.columnCount * this.rowCount;
249                     }
250                 }
251                 else
252                 {
253                     // process one double value
254                     this.buffer.append(String.format("value at row %d column %d: ", this.currentRow, this.currentColumn));
255                     this.buffer.append(this.endianUtil.decodeDouble(this.dataElementBytes, 0));
256                     this.positionInData = 0;
257                     this.currentColumn++;
258                     if (this.currentColumn >= this.columnCount)
259                     {
260                         this.currentColumn = 0;
261                         this.currentRow++;
262                     }
263                     this.buffer.append(" ");
264                     this.nextDataElementByte = 0;
265                 }
266             }
267             else if (this.currentSerializer.dataClassName().startsWith("Djunits"))
268             {
269                 if (this.currentSerializer.getNumberOfDimensions() > 0 && 0 == this.rowCount)
270                 {
271                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
272                     this.currentRow = 0;
273                     this.currentColumn = 0;
274                     if (this.dataElementBytes.length == 8)
275                     {
276                         this.rowCount = this.columnCount;
277                         this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
278                         this.buffer.append(String.format("height %d, width %d", this.rowCount, this.columnCount));
279                     }
280                     else
281                     {
282                         this.rowCount = 1;
283                         this.buffer.append(String.format("length %d", this.columnCount));
284                     }
285                     // Prepare for the unit.
286                     prepareForDataElement(2);
287                     this.totalDataSize += 2;
288                     this.buffer.append(", ");
289                     return false;
290                 }
291                 else if (null == this.displayUnit)
292                 {
293                     this.displayUnit = TypedMessage.getUnit(this.dataElementBytes, new Pointer(), this.endianUtil);
294                     this.buffer.append("unit " + this.displayUnit);
295                     int numberOfDimensions = this.currentSerializer.getNumberOfDimensions();
296                     int elementSize = this.currentSerializer.dataClassName().contains("Float") ? 4 : 8;
297                     this.totalDataSize += elementSize * (0 == numberOfDimensions ? 1 : this.rowCount * this.columnCount);
298                     prepareForDataElement(elementSize);
299                     if (0 == numberOfDimensions)
300                     {
301                         this.buffer.append(": ");
302                     }
303                     else
304                     {
305                         result = true;
306                     }
307                 }
308                 else
309                 {
310                     // get one value
311                     int dimensions = this.currentSerializer.getNumberOfDimensions();
312                     if (dimensions == 1)
313                     {
314                         this.buffer.append(String.format("value at index %d: ", this.currentColumn));
315                     }
316                     else if (dimensions == 2)
317                     {
318                         this.buffer.append(String.format("value at row %d column %d: ", this.currentRow, this.currentColumn));
319                     }
320                     // else dimension == 0
321                     if (dimensions > 0)
322                     {
323                         this.currentColumn++;
324                         if (this.currentColumn >= this.columnCount)
325                         {
326                             this.currentColumn = 0;
327                             this.currentRow++;
328                         }
329                     }
330                     this.buffer.append(this.dataElementBytes.length == 4 ? this.endianUtil.decodeFloat(this.dataElementBytes, 0)
331                             : this.endianUtil.decodeDouble(this.dataElementBytes, 0));
332                     this.nextDataElementByte = 0;
333                     result = true;
334                 }
335             }
336             else if (this.currentSerializer instanceof FixedSizeObjectSerializer)
337             {
338                 try
339                 {
340                     Object value = this.currentSerializer.deSerialize(this.dataElementBytes, new Pointer(), this.endianUtil);
341                     this.buffer.append(value.toString());
342                 }
343                 catch (SerializationException e)
344                 {
345                     this.buffer.append("Error deserializing data");
346                 }
347             }
348             else if (this.currentSerializer.getNumberOfDimensions() > 0)
349             {
350                 if (this.rowCount == 0)
351                 {
352                     // Got the height and width of a matrix, or length of an array
353                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
354                     this.currentRow = 0;
355                     this.currentColumn = 0;
356                     if (this.dataElementBytes.length == 8)
357                     {
358                         this.rowCount = this.columnCount;
359                         this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
360                         this.buffer.append(String.format("height %d, width %d", this.rowCount, this.columnCount));
361                     }
362                     else
363                     {
364                         this.rowCount = 1;
365                         this.buffer.append(String.format("length %d", this.columnCount));
366                     }
367                     int elementSize = -1;
368                     if (this.currentSerializer instanceof ArrayOrMatrixSerializer<?, ?>)
369                     {
370                         elementSize = ((ArrayOrMatrixSerializer<?, ?>) this.currentSerializer).getElementSize();
371                     }
372                     else if (this.currentSerializer instanceof BasicPrimitiveArrayOrMatrixSerializer)
373                     {
374                         elementSize = ((BasicPrimitiveArrayOrMatrixSerializer<?>) this.currentSerializer).getElementSize();
375                     }
376                     else
377                     {
378                         throw new RuntimeException("Unhandled type of array or matrix serializer");
379                     }
380                     this.totalDataSize += elementSize * this.rowCount * this.columnCount;
381                     prepareForDataElement(elementSize);
382                     // System.out.println("Selecting element size " + elementSize + " for serializer "
383                     // + this.currentSerializer.dataClassName());
384                 }
385                 else
386                 {
387                     // Got one data element
388                     if (this.currentSerializer.getNumberOfDimensions() == 1)
389                     {
390                         this.buffer.append(String.format("value at index %d: ", this.currentColumn));
391                     }
392                     else // 2 dimensions
393                     {
394                         this.buffer.append(String.format("value at row %d column %d: ", this.currentRow, this.currentColumn));
395                     }
396                     if (this.currentSerializer instanceof ArrayOrMatrixSerializer<?, ?>)
397                     {
398                         Object value = ((ArrayOrMatrixSerializer<?, ?>) this.currentSerializer)
399                                 .deSerializeElement(this.dataElementBytes, 0, this.endianUtil);
400                         this.buffer.append(value.toString());
401                     }
402                     else if (this.currentSerializer instanceof BasicPrimitiveArrayOrMatrixSerializer)
403                     {
404                         // It looks like we'll have to do this ourselves.
405                         BasicPrimitiveArrayOrMatrixSerializer<?> basicPrimitiveArraySerializer =
406                                 (BasicPrimitiveArrayOrMatrixSerializer<?>) this.currentSerializer;
407                         switch (basicPrimitiveArraySerializer.fieldType())
408                         {
409                             case FieldTypes.BYTE_8_ARRAY:
410                             case FieldTypes.BYTE_8_MATRIX:
411                                 this.buffer.append(String.format("%02x", this.dataElementBytes[0]));
412                                 break;
413 
414                             case FieldTypes.SHORT_16_ARRAY:
415                             case FieldTypes.SHORT_16_MATRIX:
416                                 this.buffer.append(String.format("%d", this.endianUtil.decodeShort(this.dataElementBytes, 0)));
417                                 break;
418 
419                             case FieldTypes.INT_32_ARRAY:
420                             case FieldTypes.INT_32_MATRIX:
421                                 this.buffer.append(String.format("%d", this.endianUtil.decodeInt(this.dataElementBytes, 0)));
422                                 break;
423 
424                             case FieldTypes.LONG_64_ARRAY:
425                             case FieldTypes.LONG_64_MATRIX:
426                                 this.buffer.append(String.format("%d", this.endianUtil.decodeLong(this.dataElementBytes, 0)));
427                                 break;
428 
429                             case FieldTypes.FLOAT_32_ARRAY:
430                             case FieldTypes.FLOAT_32_MATRIX:
431                                 this.buffer.append(String.format("%f", this.endianUtil.decodeFloat(this.dataElementBytes, 0)));
432                                 break;
433 
434                             case FieldTypes.DOUBLE_64_ARRAY:
435                             case FieldTypes.DOUBLE_64_MATRIX:
436                                 this.buffer.append(String.format("%f", this.endianUtil.decodeDouble(this.dataElementBytes, 0)));
437                                 break;
438 
439                             case FieldTypes.BOOLEAN_8_ARRAY:
440                             case FieldTypes.BOOLEAN_8_MATRIX:
441                                 this.buffer.append(0 == this.dataElementBytes[0] ? "false" : "true");
442                                 break;
443 
444                             default:
445                                 throw new RuntimeException(
446                                         "Unhandled type of basicPrimitiveArraySerializer: " + basicPrimitiveArraySerializer);
447                         }
448                     }
449                     this.nextDataElementByte = 0;
450                     this.currentColumn++;
451                     if (this.currentColumn == this.columnCount)
452                     {
453                         this.currentColumn = 0;
454                         this.currentRow++;
455                     }
456                 }
457                 // System.out.println(
458                 // "Parsed 1 element; next element is for column " + this.currentColumn + ", row " + this.currentRow);
459                 result = true;
460             }
461         }
462         if (this.positionInData == this.totalDataSize)
463 
464         {
465             this.currentSerializer = null;
466             this.positionInData = -1;
467             this.totalDataSize = -1;
468             this.rowCount = 0;
469             this.columnCount = 0;
470             return true;
471         }
472         return result;
473     }
474 
475     /**
476      * Allocate a buffer for the next data element (or two).
477      * @param dataElementSize int; size of the buffer
478      */
479     private void prepareForDataElement(final int dataElementSize)
480     {
481         this.dataElementBytes = new byte[dataElementSize];
482         this.nextDataElementByte = 0;
483     }
484 
485     /** {@inheritDoc} */
486     @Override
487     public final boolean ignoreForIdenticalOutputCheck()
488     {
489         return false;
490     }
491 
492 }