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