001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.util.struct;
006
007import java.nio.BufferUnderflowException;
008import java.nio.ByteBuffer;
009import java.nio.ByteOrder;
010import java.nio.ReadOnlyBufferException;
011import java.nio.charset.StandardCharsets;
012
013/** Dynamic (run-time) access to a serialized raw struct. */
014public final class DynamicStruct {
015  private DynamicStruct(StructDescriptor desc, ByteBuffer data) {
016    m_desc = desc;
017    m_data = data.order(ByteOrder.LITTLE_ENDIAN);
018  }
019
020  /**
021   * Constructs a new dynamic struct object with internal storage. The descriptor must be valid. The
022   * internal storage is allocated using ByteBuffer.allocate().
023   *
024   * @param desc struct descriptor
025   * @return dynamic struct object
026   * @throws IllegalStateException if struct descriptor is invalid
027   */
028  public static DynamicStruct allocate(StructDescriptor desc) {
029    return new DynamicStruct(desc, ByteBuffer.allocate(desc.getSize()));
030  }
031
032  /**
033   * Constructs a new dynamic struct object with internal storage. The descriptor must be valid. The
034   * internal storage is allocated using ByteBuffer.allocateDirect().
035   *
036   * @param desc struct descriptor
037   * @return dynamic struct object
038   * @throws IllegalStateException if struct descriptor is invalid
039   */
040  public static DynamicStruct allocateDirect(StructDescriptor desc) {
041    return new DynamicStruct(desc, ByteBuffer.allocateDirect(desc.getSize()));
042  }
043
044  /**
045   * Constructs a new dynamic struct object. Note: the passed data buffer is not copied.
046   * Modifications to the passed buffer will be reflected in the struct and vice-versa.
047   *
048   * @param desc struct descriptor
049   * @param data byte buffer containing serialized data starting at current position
050   * @return dynamic struct object
051   */
052  public static DynamicStruct wrap(StructDescriptor desc, ByteBuffer data) {
053    return new DynamicStruct(desc, data.slice());
054  }
055
056  /**
057   * Gets the struct descriptor.
058   *
059   * @return struct descriptor
060   */
061  public StructDescriptor getDescriptor() {
062    return m_desc;
063  }
064
065  /**
066   * Gets the serialized backing data buffer.
067   *
068   * @return data buffer
069   */
070  public ByteBuffer getBuffer() {
071    return m_data.duplicate().position(0);
072  }
073
074  /**
075   * Overwrites the entire serialized struct by copying data from a byte array.
076   *
077   * @param data replacement data for the struct
078   * @throws BufferUnderflowException if data is smaller than the struct size
079   * @throws ReadOnlyBufferException if the underlying buffer is read-only
080   * @throws IllegalStateException if struct descriptor is invalid
081   */
082  public void setData(byte[] data) {
083    if (data.length < m_desc.getSize()) {
084      throw new BufferUnderflowException();
085    }
086    m_data.position(0).put(data);
087  }
088
089  /**
090   * Overwrites the entire serialized struct by copying data from a byte buffer.
091   *
092   * @param data replacement data for the struct; copy starts from current position
093   * @throws BufferUnderflowException if remaining data is smaller than the struct size
094   * @throws ReadOnlyBufferException if the underlying buffer is read-only
095   * @throws IllegalStateException if struct descriptor is invalid
096   */
097  public void setData(ByteBuffer data) {
098    if (data.remaining() < m_desc.getSize()) {
099      throw new BufferUnderflowException();
100    }
101    int oldLimit = data.limit();
102    m_data.position(0).put(data.limit(m_desc.getSize()));
103    data.limit(oldLimit);
104  }
105
106  /**
107   * Gets a struct field descriptor by name.
108   *
109   * @param name field name
110   * @return field descriptor, or null if no field with that name exists
111   */
112  public StructFieldDescriptor findField(String name) {
113    return m_desc.findFieldByName(name);
114  }
115
116  /**
117   * Gets the value of a boolean field.
118   *
119   * @param field field descriptor
120   * @param arrIndex array index (must be less than field array size)
121   * @return boolean field value
122   * @throws UnsupportedOperationException if field is not bool type
123   * @throws IllegalArgumentException if field is not a member of this struct
124   * @throws IllegalStateException if struct descriptor is invalid
125   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
126   */
127  public boolean getBoolField(StructFieldDescriptor field, int arrIndex) {
128    if (field.getType() != StructFieldType.kBool) {
129      throw new UnsupportedOperationException("field is not bool type");
130    }
131    return getFieldImpl(field, arrIndex) != 0;
132  }
133
134  /**
135   * Gets the value of a boolean field.
136   *
137   * @param field field descriptor
138   * @return boolean field value
139   * @throws UnsupportedOperationException if field is not bool type
140   * @throws IllegalArgumentException if field is not a member of this struct
141   * @throws IllegalStateException if struct descriptor is invalid
142   */
143  public boolean getBoolField(StructFieldDescriptor field) {
144    return getBoolField(field, 0);
145  }
146
147  /**
148   * Sets the value of a boolean field.
149   *
150   * @param field field descriptor
151   * @param value boolean value
152   * @param arrIndex array index (must be less than field array size)
153   * @throws UnsupportedOperationException if field is not bool type
154   * @throws IllegalArgumentException if field is not a member of this struct
155   * @throws IllegalStateException if struct descriptor is invalid
156   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
157   * @throws ReadOnlyBufferException if the underlying buffer is read-only
158   */
159  public void setBoolField(StructFieldDescriptor field, boolean value, int arrIndex) {
160    if (field.getType() != StructFieldType.kBool) {
161      throw new UnsupportedOperationException("field is not bool type");
162    }
163    setFieldImpl(field, value ? 1 : 0, arrIndex);
164  }
165
166  /**
167   * Sets the value of a boolean field.
168   *
169   * @param field field descriptor
170   * @param value boolean value
171   * @throws UnsupportedOperationException if field is not bool type
172   * @throws IllegalArgumentException if field is not a member of this struct
173   * @throws IllegalStateException if struct descriptor is invalid
174   * @throws ReadOnlyBufferException if the underlying buffer is read-only
175   */
176  public void setBoolField(StructFieldDescriptor field, boolean value) {
177    setBoolField(field, value, 0);
178  }
179
180  /**
181   * Gets the value of an integer field.
182   *
183   * @param field field descriptor
184   * @param arrIndex array index (must be less than field array size)
185   * @return integer field value
186   * @throws UnsupportedOperationException if field is not integer type
187   * @throws IllegalArgumentException if field is not a member of this struct
188   * @throws IllegalStateException if struct descriptor is invalid
189   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
190   */
191  public long getIntField(StructFieldDescriptor field, int arrIndex) {
192    if (!field.isInt() && !field.isUint()) {
193      throw new UnsupportedOperationException("field is not integer type");
194    }
195    return getFieldImpl(field, arrIndex);
196  }
197
198  /**
199   * Gets the value of an integer field.
200   *
201   * @param field field descriptor
202   * @return integer field value
203   * @throws UnsupportedOperationException if field is not integer type
204   * @throws IllegalArgumentException if field is not a member of this struct
205   * @throws IllegalStateException if struct descriptor is invalid
206   */
207  public long getIntField(StructFieldDescriptor field) {
208    return getIntField(field, 0);
209  }
210
211  /**
212   * Sets the value of an integer field.
213   *
214   * @param field field descriptor
215   * @param value integer value
216   * @param arrIndex array index (must be less than field array size)
217   * @throws UnsupportedOperationException if field is not integer type
218   * @throws IllegalArgumentException if field is not a member of this struct
219   * @throws IllegalStateException if struct descriptor is invalid
220   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
221   * @throws ReadOnlyBufferException if the underlying buffer is read-only
222   */
223  public void setIntField(StructFieldDescriptor field, long value, int arrIndex) {
224    if (!field.isInt() && !field.isUint()) {
225      throw new UnsupportedOperationException("field is not integer type");
226    }
227    setFieldImpl(field, value, arrIndex);
228  }
229
230  /**
231   * Sets the value of an integer field.
232   *
233   * @param field field descriptor
234   * @param value integer value
235   * @throws UnsupportedOperationException if field is not integer type
236   * @throws IllegalArgumentException if field is not a member of this struct
237   * @throws IllegalStateException if struct descriptor is invalid
238   * @throws ReadOnlyBufferException if the underlying buffer is read-only
239   */
240  public void setIntField(StructFieldDescriptor field, long value) {
241    setIntField(field, value, 0);
242  }
243
244  /**
245   * Gets the value of a float field.
246   *
247   * @param field field descriptor
248   * @param arrIndex array index (must be less than field array size)
249   * @return float field value
250   * @throws UnsupportedOperationException if field is not float type
251   * @throws IllegalArgumentException if field is not a member of this struct
252   * @throws IllegalStateException if struct descriptor is invalid
253   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
254   */
255  public float getFloatField(StructFieldDescriptor field, int arrIndex) {
256    if (field.getType() != StructFieldType.kFloat) {
257      throw new UnsupportedOperationException("field is not float type");
258    }
259    return Float.intBitsToFloat((int) getFieldImpl(field, arrIndex));
260  }
261
262  /**
263   * Gets the value of a float field.
264   *
265   * @param field field descriptor
266   * @return float field value
267   * @throws UnsupportedOperationException if field is not float type
268   * @throws IllegalArgumentException if field is not a member of this struct
269   * @throws IllegalStateException if struct descriptor is invalid
270   */
271  public float getFloatField(StructFieldDescriptor field) {
272    return getFloatField(field, 0);
273  }
274
275  /**
276   * Sets the value of a float field.
277   *
278   * @param field field descriptor
279   * @param value float value
280   * @param arrIndex array index (must be less than field array size)
281   * @throws UnsupportedOperationException if field is not float type
282   * @throws IllegalArgumentException if field is not a member of this struct
283   * @throws IllegalStateException if struct descriptor is invalid
284   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
285   * @throws ReadOnlyBufferException if the underlying buffer is read-only
286   */
287  public void setFloatField(StructFieldDescriptor field, float value, int arrIndex) {
288    if (field.getType() != StructFieldType.kFloat) {
289      throw new UnsupportedOperationException("field is not float type");
290    }
291    setFieldImpl(field, Float.floatToIntBits(value), arrIndex);
292  }
293
294  /**
295   * Sets the value of a float field.
296   *
297   * @param field field descriptor
298   * @param value float value
299   * @throws UnsupportedOperationException if field is not float type
300   * @throws IllegalArgumentException if field is not a member of this struct
301   * @throws IllegalStateException if struct descriptor is invalid
302   * @throws ReadOnlyBufferException if the underlying buffer is read-only
303   */
304  public void setFloatField(StructFieldDescriptor field, float value) {
305    setFloatField(field, value, 0);
306  }
307
308  /**
309   * Gets the value of a double field.
310   *
311   * @param field field descriptor
312   * @param arrIndex array index (must be less than field array size)
313   * @return double field value
314   * @throws UnsupportedOperationException if field is not double type
315   * @throws IllegalArgumentException if field is not a member of this struct
316   * @throws IllegalStateException if struct descriptor is invalid
317   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
318   */
319  public double getDoubleField(StructFieldDescriptor field, int arrIndex) {
320    if (field.getType() != StructFieldType.kDouble) {
321      throw new UnsupportedOperationException("field is not double type");
322    }
323    return Double.longBitsToDouble(getFieldImpl(field, arrIndex));
324  }
325
326  /**
327   * Gets the value of a double field.
328   *
329   * @param field field descriptor
330   * @return double field value
331   * @throws UnsupportedOperationException if field is not double type
332   * @throws IllegalArgumentException if field is not a member of this struct
333   * @throws IllegalStateException if struct descriptor is invalid
334   */
335  public double getDoubleField(StructFieldDescriptor field) {
336    return getDoubleField(field, 0);
337  }
338
339  /**
340   * Sets the value of a double field.
341   *
342   * @param field field descriptor
343   * @param value double value
344   * @param arrIndex array index (must be less than field array size)
345   * @throws UnsupportedOperationException if field is not double type
346   * @throws IllegalArgumentException if field is not a member of this struct
347   * @throws IllegalStateException if struct descriptor is invalid
348   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
349   * @throws ReadOnlyBufferException if the underlying buffer is read-only
350   */
351  public void setDoubleField(StructFieldDescriptor field, double value, int arrIndex) {
352    if (field.getType() != StructFieldType.kDouble) {
353      throw new UnsupportedOperationException("field is not double type");
354    }
355    setFieldImpl(field, Double.doubleToLongBits(value), arrIndex);
356  }
357
358  /**
359   * Sets the value of a double field.
360   *
361   * @param field field descriptor
362   * @param value double value
363   * @throws UnsupportedOperationException if field is not double type
364   * @throws IllegalArgumentException if field is not a member of this struct
365   * @throws IllegalStateException if struct descriptor is invalid
366   * @throws ReadOnlyBufferException if the underlying buffer is read-only
367   */
368  public void setDoubleField(StructFieldDescriptor field, double value) {
369    setDoubleField(field, value, 0);
370  }
371
372  /**
373   * Gets the value of a character or character array field.
374   *
375   * @param field field descriptor
376   * @return field value
377   * @throws UnsupportedOperationException if field is not char type
378   * @throws IllegalArgumentException if field is not a member of this struct
379   * @throws IllegalStateException if struct descriptor is invalid
380   */
381  public String getStringField(StructFieldDescriptor field) {
382    if (field.getType() != StructFieldType.kChar) {
383      throw new UnsupportedOperationException("field is not char type");
384    }
385    if (!field.getParent().equals(m_desc)) {
386      throw new IllegalArgumentException("field is not part of this struct");
387    }
388    if (!m_desc.isValid()) {
389      throw new IllegalStateException("struct descriptor is not valid");
390    }
391    byte[] bytes = new byte[field.m_arraySize];
392    m_data.position(field.m_offset).get(bytes, 0, field.m_arraySize);
393    return new String(bytes, StandardCharsets.UTF_8);
394  }
395
396  /**
397   * Sets the value of a character or character array field.
398   *
399   * @param field field descriptor
400   * @param value field value
401   * @throws UnsupportedOperationException if field is not char type
402   * @throws IllegalArgumentException if field is not a member of this struct
403   * @throws IllegalStateException if struct descriptor is invalid
404   */
405  public void setStringField(StructFieldDescriptor field, String value) {
406    if (field.getType() != StructFieldType.kChar) {
407      throw new UnsupportedOperationException("field is not char type");
408    }
409    if (!field.getParent().equals(m_desc)) {
410      throw new IllegalArgumentException("field is not part of this struct");
411    }
412    if (!m_desc.isValid()) {
413      throw new IllegalStateException("struct descriptor is not valid");
414    }
415    ByteBuffer bb = StandardCharsets.UTF_8.encode(value);
416    int len = Math.min(bb.remaining(), field.m_arraySize);
417    m_data.position(field.m_offset).put(bb.limit(len));
418    for (int i = len; i < field.m_arraySize; i++) {
419      m_data.put((byte) 0);
420    }
421  }
422
423  /**
424   * Gets the value of a struct field.
425   *
426   * @param field field descriptor
427   * @param arrIndex array index (must be less than field array size)
428   * @return field value
429   * @throws UnsupportedOperationException if field is not of struct type
430   * @throws IllegalArgumentException if field is not a member of this struct
431   * @throws IllegalStateException if struct descriptor is invalid
432   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
433   */
434  public DynamicStruct getStructField(StructFieldDescriptor field, int arrIndex) {
435    if (field.getType() != StructFieldType.kStruct) {
436      throw new UnsupportedOperationException("field is not struct type");
437    }
438    if (!field.getParent().equals(m_desc)) {
439      throw new IllegalArgumentException("field is not part of this struct");
440    }
441    if (!m_desc.isValid()) {
442      throw new IllegalStateException("struct descriptor is not valid");
443    }
444    if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
445      throw new ArrayIndexOutOfBoundsException(
446          "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
447    }
448    StructDescriptor struct = field.getStruct();
449    return wrap(struct, m_data.position(field.m_offset + arrIndex * struct.m_size));
450  }
451
452  /**
453   * Gets the value of a struct field.
454   *
455   * @param field field descriptor
456   * @return field value
457   * @throws UnsupportedOperationException if field is not of struct type
458   * @throws IllegalArgumentException if field is not a member of this struct
459   * @throws IllegalStateException if struct descriptor is invalid
460   */
461  public DynamicStruct getStructField(StructFieldDescriptor field) {
462    return getStructField(field, 0);
463  }
464
465  /**
466   * Sets the value of a struct field.
467   *
468   * @param field field descriptor
469   * @param value struct value
470   * @param arrIndex array index (must be less than field array size)
471   * @throws UnsupportedOperationException if field is not struct type
472   * @throws IllegalArgumentException if field is not a member of this struct
473   * @throws IllegalStateException if struct descriptor is invalid
474   * @throws ArrayIndexOutOfBoundsException if array index is out of bounds
475   * @throws ReadOnlyBufferException if the underlying buffer is read-only
476   */
477  public void setStructField(StructFieldDescriptor field, DynamicStruct value, int arrIndex) {
478    if (field.getType() != StructFieldType.kStruct) {
479      throw new UnsupportedOperationException("field is not struct type");
480    }
481    if (!field.getParent().equals(m_desc)) {
482      throw new IllegalArgumentException("field is not part of this struct");
483    }
484    if (!m_desc.isValid()) {
485      throw new IllegalStateException("struct descriptor is not valid");
486    }
487    StructDescriptor struct = field.getStruct();
488    if (!value.getDescriptor().equals(struct)) {
489      throw new IllegalArgumentException("value's struct type does not match field struct type");
490    }
491    if (!value.getDescriptor().isValid()) {
492      throw new IllegalStateException("value's struct descriptor is not valid");
493    }
494    if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
495      throw new ArrayIndexOutOfBoundsException(
496          "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
497    }
498    m_data
499        .position(field.m_offset + arrIndex * struct.m_size)
500        .put(value.m_data.position(0).limit(value.getDescriptor().getSize()));
501  }
502
503  /**
504   * Sets the value of a struct field.
505   *
506   * @param field field descriptor
507   * @param value struct value
508   * @throws UnsupportedOperationException if field is not struct type
509   * @throws IllegalArgumentException if field is not a member of this struct
510   * @throws IllegalStateException if struct descriptor is invalid
511   * @throws ReadOnlyBufferException if the underlying buffer is read-only
512   */
513  public void setStructField(StructFieldDescriptor field, DynamicStruct value) {
514    setStructField(field, value, 0);
515  }
516
517  private long getFieldImpl(StructFieldDescriptor field, int arrIndex) {
518    if (!field.getParent().equals(m_desc)) {
519      throw new IllegalArgumentException("field is not part of this struct");
520    }
521    if (!m_desc.isValid()) {
522      throw new IllegalStateException("struct descriptor is not valid");
523    }
524    if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
525      throw new ArrayIndexOutOfBoundsException(
526          "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
527    }
528
529    long val;
530    switch (field.m_size) {
531      case 1:
532        val = m_data.get(field.m_offset + arrIndex);
533        break;
534      case 2:
535        val = m_data.getShort(field.m_offset + arrIndex * 2);
536        break;
537      case 4:
538        val = m_data.getInt(field.m_offset + arrIndex * 4);
539        break;
540      case 8:
541        val = m_data.getLong(field.m_offset + arrIndex * 8);
542        break;
543      default:
544        throw new IllegalStateException("invalid field size");
545    }
546
547    if (field.isUint() || field.getType() == StructFieldType.kBool) {
548      // for unsigned fields, we can simply logical shift and mask
549      return (val >>> field.m_bitShift) & field.getBitMask();
550    } else {
551      // to get sign extension, shift so the sign bit within the bitfield goes to the long's sign
552      // bit (also clearing all higher bits), then shift back down (also clearing all lower bits);
553      // since upper and lower bits are cleared with the shifts, the bitmask is unnecessary
554      return (val << (64 - field.m_bitShift - field.getBitWidth())) >> (64 - field.getBitWidth());
555    }
556  }
557
558  private void setFieldImpl(StructFieldDescriptor field, long value, int arrIndex) {
559    if (!field.getParent().equals(m_desc)) {
560      throw new IllegalArgumentException("field is not part of this struct");
561    }
562    if (!m_desc.isValid()) {
563      throw new IllegalStateException("struct descriptor is not valid");
564    }
565    if (arrIndex < 0 || arrIndex >= field.m_arraySize) {
566      throw new ArrayIndexOutOfBoundsException(
567          "arrIndex (" + arrIndex + ") is larger than array size (" + field.m_arraySize + ")");
568    }
569
570    // common case is no bit shift and no masking
571    if (!field.isBitField()) {
572      switch (field.m_size) {
573        case 1:
574          m_data.put(field.m_offset + arrIndex, (byte) value);
575          break;
576        case 2:
577          m_data.putShort(field.m_offset + arrIndex * 2, (short) value);
578          break;
579        case 4:
580          m_data.putInt(field.m_offset + arrIndex * 4, (int) value);
581          break;
582        case 8:
583          m_data.putLong(field.m_offset + arrIndex * 8, value);
584          break;
585        default:
586          throw new IllegalStateException("invalid field size");
587      }
588      return;
589    }
590
591    // handle bit shifting and masking into current value
592    switch (field.m_size) {
593      case 1:
594        {
595          byte val = m_data.get(field.m_offset + arrIndex);
596          val &= (byte) ~(field.getBitMask() << field.m_bitShift);
597          val |= (byte) ((value & field.getBitMask()) << field.m_bitShift);
598          m_data.put(field.m_offset + arrIndex, val);
599          break;
600        }
601      case 2:
602        {
603          short val = m_data.getShort(field.m_offset + arrIndex * 2);
604          val &= (short) ~(field.getBitMask() << field.m_bitShift);
605          val |= (short) ((value & field.getBitMask()) << field.m_bitShift);
606          m_data.putShort(field.m_offset + arrIndex * 2, val);
607          break;
608        }
609      case 4:
610        {
611          int val = m_data.getInt(field.m_offset + arrIndex * 4);
612          val &= (int) ~(field.getBitMask() << field.m_bitShift);
613          val |= (int) ((value & field.getBitMask()) << field.m_bitShift);
614          m_data.putInt(field.m_offset + arrIndex * 4, val);
615          break;
616        }
617      case 8:
618        {
619          long val = m_data.getLong(field.m_offset + arrIndex * 8);
620          val &= ~(field.getBitMask() << field.m_bitShift);
621          val |= (value & field.getBitMask()) << field.m_bitShift;
622          m_data.putLong(field.m_offset + arrIndex * 8, val);
623          break;
624        }
625      default:
626        throw new IllegalStateException("invalid field size");
627    }
628  }
629
630  private final StructDescriptor m_desc;
631  private final ByteBuffer m_data;
632}