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="https://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="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
16   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
17   * @author <a href="https://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 80;
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() + (this.currentSerializer.getNumberOfDimensions() > 0
101                         || this.currentSerializer.dataClassName().startsWith("Djunits") ? " " : ": "));
102                 this.positionInData = 1;
103                 this.totalDataSize = 1; // to be adjusted
104                 this.columnCount = 0;
105                 this.rowCount = 0;
106                 this.displayUnit = null;
107                 if (this.currentSerializer.dataClassName().startsWith("String_"))
108                 {
109                     prepareForDataElement(4);
110                     this.totalDataSize += 4;
111                 }
112                 else if (this.currentSerializer.dataClassName().contentEquals("Djunits_vector_array"))
113                 {
114                     prepareForDataElement(8);
115                     this.totalDataSize += 8;
116                 }
117                 else if (this.currentSerializer.getNumberOfDimensions() > 0)
118                 {
119                     int size = this.currentSerializer.getNumberOfDimensions() * 4;
120                     prepareForDataElement(size);
121                     this.totalDataSize += size;
122                 }
123                 else if (this.currentSerializer instanceof ObjectSerializer)
124                 {
125                     try
126                     {
127                         int size;
128                         if (this.currentSerializer.dataClassName().startsWith("Djunits"))
129                         {
130                             // We won't get away calling the size method with null here
131                             // Prepare to get the display unit; requires at least two more bytes
132                             size = 2;
133                             this.displayUnit = null;
134                         }
135                         else
136                         {
137                             size = this.currentSerializer.size(null);
138                         }
139                         prepareForDataElement(size);
140                         this.totalDataSize += size;
141                     }
142                     catch (SerializationException e)
143                     {
144                         e.printStackTrace();
145                     }
146                 }
147                 else
148                 {
149                     try
150                     {
151                         int size = this.currentSerializer.size(null);
152                         this.totalDataSize += size;
153                         prepareForDataElement(size);
154                     }
155                     catch (SerializationException e)
156                     {
157                         e.printStackTrace(); // Cannot happen
158                     }
159                 }
160             }
161             return result;
162         }
163         if (this.nextDataElementByte < this.dataElementBytes.length)
164         {
165             this.dataElementBytes[this.nextDataElementByte] = theByte;
166         }
167         this.nextDataElementByte++;
168         this.positionInData++;
169         if (this.nextDataElementByte == this.dataElementBytes.length)
170         {
171             if (this.currentSerializer.dataClassName().startsWith("String_"))
172             {
173                 int elementSize = this.currentSerializer.dataClassName().endsWith("8") ? 1 : 2;
174                 if (this.columnCount == 0) // re-using columnCount to store number of characters
175                 {
176                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
177                     prepareForDataElement(elementSize);
178                     this.totalDataSize += this.columnCount * elementSize;
179                 }
180                 else
181                 {
182                     if (1 == elementSize)
183                     {
184                         if (this.dataElementBytes[0] > 32 && this.dataElementBytes[0] < 127)
185                         {
186                             this.buffer.append((char) this.dataElementBytes[0]); // safe to print
187                         }
188                         else
189                         {
190                             this.buffer.append("."); // not safe to print
191                         }
192                     }
193                     else
194                     {
195                         char character = this.endianUtil.decodeChar(this.dataElementBytes, 0);
196                         if (Character.isAlphabetic(character))
197                         {
198                             this.buffer.append(character); // safe to print
199                         }
200                         else
201                         {
202                             this.buffer.append("."); // not safe to print
203                         }
204                     }
205                 }
206                 this.currentColumn = 0;
207                 this.nextDataElementByte = 0;
208             }
209             else if (this.currentSerializer.dataClassName().contentEquals("Djunits_vector_array"))
210             {
211                 if (this.rowCount == 0)
212                 {
213                     this.rowCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
214                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
215                     this.currentRow = -1; // indicates we are parsing the units
216                     this.currentColumn = 0;
217                     prepareForDataElement(2);
218                     this.totalDataSize += 2;
219                 }
220                 else if (this.currentRow < 0)
221                 {
222                     // parse one unit
223                     if (checkMoneyNeedsMoreBytes())
224                     {
225                         return false;
226                     }
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                     if (checkMoneyNeedsMoreBytes())
290                     {
291                         return false;
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      * Check if additional bytes are needed to decode a money unit. Increases the buffer and the <code>totalDataSize</code> to
477      * accommodate all needed bytes.
478      * @return boolean; true if more bytes are needed; false if no more bytes are needed.
479      */
480     private boolean checkMoneyNeedsMoreBytes()
481     {
482         if (this.dataElementBytes[0] < 100 || this.dataElementBytes[0] > 106 || this.dataElementBytes.length != 2)
483         {
484             return false;
485         }
486         int requiredLength = this.dataElementBytes[0] == 100 ? 3 : 4;
487         this.totalDataSize += requiredLength - this.dataElementBytes.length;
488         this.dataElementBytes = java.util.Arrays.copyOf(this.dataElementBytes, requiredLength);
489         return true;
490     }
491 
492     /**
493      * Allocate a buffer for the next data element (or two).
494      * @param dataElementSize int; size of the buffer
495      */
496     private void prepareForDataElement(final int dataElementSize)
497     {
498         this.dataElementBytes = new byte[dataElementSize];
499         this.nextDataElementByte = 0;
500     }
501 
502     @Override
503     public final boolean ignoreForIdenticalOutputCheck()
504     {
505         return false;
506     }
507 
508 }