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