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