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      @Override
76      public final String getResult()
77      {
78          String result = this.buffer.toString();
79          this.buffer.setLength(0);
80          return result;
81      }
82  
83      @Override
84      public final int getMaximumWidth()
85      {
86          return 80;
87      }
88  
89      @Override
90      public final boolean append(final int address, final byte theByte) throws IOException
91      {
92          boolean result = false;
93          if (null == this.currentSerializer)
94          {
95              // We are expecting a field type byte
96              this.currentFieldType = theByte;
97              this.currentSerializer = TypedMessage.PRIMITIVE_DATA_DECODERS.get(this.currentFieldType);
98              if (null == this.currentSerializer)
99              {
100                 this.buffer.append(String.format("Bad field type %02x - resynchronizing", this.currentFieldType));
101                 result = true;
102                 // May eventually re-synchronize, but that could take a lot of data.
103             }
104             else
105             {
106                 this.buffer.append(this.currentSerializer.dataClassName() + (this.currentSerializer.getNumberOfDimensions() > 0
107                         || this.currentSerializer.dataClassName().startsWith("Djunits") ? " " : ": "));
108                 this.positionInData = 1;
109                 this.totalDataSize = 1; // to be adjusted
110                 this.columnCount = 0;
111                 this.rowCount = 0;
112                 this.displayUnit = null;
113                 if (this.currentSerializer.dataClassName().startsWith("String_"))
114                 {
115                     prepareForDataElement(4);
116                     this.totalDataSize += 4;
117                 }
118                 else if (this.currentSerializer.dataClassName().contentEquals("Djunits_vector_array"))
119                 {
120                     prepareForDataElement(8);
121                     this.totalDataSize += 8;
122                 }
123                 else if (this.currentSerializer.getNumberOfDimensions() > 0)
124                 {
125                     int size = this.currentSerializer.getNumberOfDimensions() * 4;
126                     prepareForDataElement(size);
127                     this.totalDataSize += size;
128                 }
129                 else if (this.currentSerializer instanceof ObjectSerializer)
130                 {
131                     try
132                     {
133                         int size;
134                         if (this.currentSerializer.dataClassName().startsWith("Djunits"))
135                         {
136                             // We won't get away calling the size method with null here
137                             // Prepare to get the display unit; requires at least two more bytes
138                             size = 2;
139                             this.displayUnit = null;
140                         }
141                         else
142                         {
143                             size = this.currentSerializer.size(null);
144                         }
145                         prepareForDataElement(size);
146                         this.totalDataSize += size;
147                     }
148                     catch (SerializationException e)
149                     {
150                         e.printStackTrace();
151                     }
152                 }
153                 else
154                 {
155                     try
156                     {
157                         int size = this.currentSerializer.size(null);
158                         this.totalDataSize += size;
159                         prepareForDataElement(size);
160                     }
161                     catch (SerializationException e)
162                     {
163                         e.printStackTrace(); // Cannot happen
164                     }
165                 }
166             }
167             return result;
168         }
169         if (this.nextDataElementByte < this.dataElementBytes.length)
170         {
171             this.dataElementBytes[this.nextDataElementByte] = theByte;
172         }
173         this.nextDataElementByte++;
174         this.positionInData++;
175         if (this.nextDataElementByte == this.dataElementBytes.length)
176         {
177             if (this.currentSerializer.dataClassName().startsWith("String_"))
178             {
179                 int elementSize = this.currentSerializer.dataClassName().endsWith("8") ? 1 : 2;
180                 if (this.columnCount == 0) // re-using columnCount to store number of characters
181                 {
182                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
183                     prepareForDataElement(elementSize);
184                     this.totalDataSize += this.columnCount * elementSize;
185                 }
186                 else
187                 {
188                     if (1 == elementSize)
189                     {
190                         if (this.dataElementBytes[0] > 32 && this.dataElementBytes[0] < 127)
191                         {
192                             this.buffer.append((char) this.dataElementBytes[0]); // safe to print
193                         }
194                         else
195                         {
196                             this.buffer.append("."); // not safe to print
197                         }
198                     }
199                     else
200                     {
201                         char character = this.endianUtil.decodeChar(this.dataElementBytes, 0);
202                         if (Character.isAlphabetic(character))
203                         {
204                             this.buffer.append(character); // safe to print
205                         }
206                         else
207                         {
208                             this.buffer.append("."); // not safe to print
209                         }
210                     }
211                 }
212                 this.currentColumn = 0;
213                 this.nextDataElementByte = 0;
214             }
215             else if (this.currentSerializer.dataClassName().contentEquals("Djunits_vector_array"))
216             {
217                 if (this.rowCount == 0)
218                 {
219                     this.rowCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
220                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
221                     this.currentRow = -1; // indicates we are parsing the units
222                     this.currentColumn = 0;
223                     prepareForDataElement(2);
224                     this.totalDataSize += 2;
225                 }
226                 else if (this.currentRow < 0)
227                 {
228                     // parse one unit
229                     TypedMessage.getUnit(this.dataElementBytes, new Pointer(), this.endianUtil);
230                     this.displayUnit = TypedMessage.getUnit(this.dataElementBytes, new Pointer(), this.endianUtil);
231                     this.buffer.append("unit for column " + this.currentColumn + ": ");
232                     this.buffer.append(this.displayUnit);
233                     this.currentColumn++;
234                     if (this.currentColumn < this.columnCount)
235                     {
236                         prepareForDataElement(2);
237                         this.totalDataSize += 2;
238                         this.buffer.append(", ");
239                     }
240                     else
241                     {
242                         // Done with the units; prepare to parse the values
243                         this.currentRow = 0;
244                         this.currentColumn = 0;
245                         prepareForDataElement(8);
246                         this.totalDataSize += 8 * this.columnCount * this.rowCount;
247                     }
248                 }
249                 else
250                 {
251                     // process one double value
252                     this.buffer.append(String.format("value at row %d column %d: ", this.currentRow, this.currentColumn));
253                     this.buffer.append(this.endianUtil.decodeDouble(this.dataElementBytes, 0));
254                     this.positionInData = 0;
255                     this.currentColumn++;
256                     if (this.currentColumn >= this.columnCount)
257                     {
258                         this.currentColumn = 0;
259                         this.currentRow++;
260                     }
261                     this.buffer.append(" ");
262                     this.nextDataElementByte = 0;
263                 }
264             }
265             else if (this.currentSerializer.dataClassName().startsWith("Djunits"))
266             {
267                 if (this.currentSerializer.getNumberOfDimensions() > 0 && 0 == this.rowCount)
268                 {
269                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
270                     this.currentRow = 0;
271                     this.currentColumn = 0;
272                     if (this.dataElementBytes.length == 8)
273                     {
274                         this.rowCount = this.columnCount;
275                         this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
276                         this.buffer.append(String.format("height %d, width %d", this.rowCount, this.columnCount));
277                     }
278                     else
279                     {
280                         this.rowCount = 1;
281                         this.buffer.append(String.format("length %d", this.columnCount));
282                     }
283                     // Prepare for the unit.
284                     prepareForDataElement(2);
285                     this.totalDataSize += 2;
286                     this.buffer.append(", ");
287                     return false;
288                 }
289                 else if (null == this.displayUnit)
290                 {
291                     this.displayUnit = TypedMessage.getUnit(this.dataElementBytes, new Pointer(), this.endianUtil);
292                     this.buffer.append("unit " + this.displayUnit);
293                     int numberOfDimensions = this.currentSerializer.getNumberOfDimensions();
294                     int elementSize = this.currentSerializer.dataClassName().contains("Float") ? 4 : 8;
295                     this.totalDataSize += elementSize * (0 == numberOfDimensions ? 1 : this.rowCount * this.columnCount);
296                     prepareForDataElement(elementSize);
297                     if (0 == numberOfDimensions)
298                     {
299                         this.buffer.append(": ");
300                     }
301                     else
302                     {
303                         result = true;
304                     }
305                 }
306                 else
307                 {
308                     // get one value
309                     int dimensions = this.currentSerializer.getNumberOfDimensions();
310                     if (dimensions == 1)
311                     {
312                         this.buffer.append(String.format("value at index %d: ", this.currentColumn));
313                     }
314                     else if (dimensions == 2)
315                     {
316                         this.buffer.append(String.format("value at row %d column %d: ", this.currentRow, this.currentColumn));
317                     }
318                     // else dimension == 0
319                     if (dimensions > 0)
320                     {
321                         this.currentColumn++;
322                         if (this.currentColumn >= this.columnCount)
323                         {
324                             this.currentColumn = 0;
325                             this.currentRow++;
326                         }
327                     }
328                     this.buffer.append(this.dataElementBytes.length == 4 ? this.endianUtil.decodeFloat(this.dataElementBytes, 0)
329                             : this.endianUtil.decodeDouble(this.dataElementBytes, 0));
330                     this.nextDataElementByte = 0;
331                     result = true;
332                 }
333             }
334             else if (this.currentSerializer instanceof FixedSizeObjectSerializer)
335             {
336                 try
337                 {
338                     Object value = this.currentSerializer.deSerialize(this.dataElementBytes, new Pointer(), this.endianUtil);
339                     this.buffer.append(value.toString());
340                 }
341                 catch (SerializationException e)
342                 {
343                     this.buffer.append("Error deserializing data");
344                 }
345             }
346             else if (this.currentSerializer.getNumberOfDimensions() > 0)
347             {
348                 if (this.rowCount == 0)
349                 {
350                     // Got the height and width of a matrix, or length of an array
351                     this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 0);
352                     this.currentRow = 0;
353                     this.currentColumn = 0;
354                     if (this.dataElementBytes.length == 8)
355                     {
356                         this.rowCount = this.columnCount;
357                         this.columnCount = this.endianUtil.decodeInt(this.dataElementBytes, 4);
358                         this.buffer.append(String.format("height %d, width %d", this.rowCount, this.columnCount));
359                     }
360                     else
361                     {
362                         this.rowCount = 1;
363                         this.buffer.append(String.format("length %d", 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      * Allocate a buffer for the next data element (or two).
475      * @param dataElementSize int; size of the buffer
476      */
477     private void prepareForDataElement(final int dataElementSize)
478     {
479         this.dataElementBytes = new byte[dataElementSize];
480         this.nextDataElementByte = 0;
481     }
482 
483     @Override
484     public final boolean ignoreForIdenticalOutputCheck()
485     {
486         return false;
487     }
488 
489 }