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.lang.reflect.Array;
008import java.nio.ByteBuffer;
009import java.nio.ByteOrder;
010import java.util.Collection;
011
012/**
013 * Reusable buffer for serialization/deserialization to/from a raw struct.
014 *
015 * @param <T> object type
016 */
017public final class StructBuffer<T> {
018  private StructBuffer(Struct<T> struct) {
019    m_structSize = struct.getSize();
020    m_buf = ByteBuffer.allocateDirect(m_structSize).order(ByteOrder.LITTLE_ENDIAN);
021    m_struct = struct;
022  }
023
024  public static <T> StructBuffer<T> create(Struct<T> struct) {
025    return new StructBuffer<>(struct);
026  }
027
028  /**
029   * Gets the struct object of the stored type.
030   *
031   * @return struct object
032   */
033  public Struct<T> getStruct() {
034    return m_struct;
035  }
036
037  /**
038   * Gets the type string.
039   *
040   * @return type string
041   */
042  public String getTypeString() {
043    return m_struct.getTypeString();
044  }
045
046  /**
047   * Ensures sufficient buffer space is available for the given number of elements.
048   *
049   * @param nelem number of elements
050   */
051  public void reserve(int nelem) {
052    if ((nelem * m_structSize) > m_buf.capacity()) {
053      m_buf = ByteBuffer.allocateDirect(nelem * m_structSize).order(ByteOrder.LITTLE_ENDIAN);
054    }
055  }
056
057  /**
058   * Serializes a value to a ByteBuffer. The returned ByteBuffer is a direct byte buffer with the
059   * position set to the end of the serialized data.
060   *
061   * @param value value
062   * @return byte buffer
063   */
064  public ByteBuffer write(T value) {
065    m_buf.position(0);
066    m_struct.pack(m_buf, value);
067    return m_buf;
068  }
069
070  /**
071   * Deserializes a value from a byte array, creating a new object.
072   *
073   * @param buf byte array
074   * @param start starting location within byte array
075   * @param len length of serialized data
076   * @return new object
077   */
078  public T read(byte[] buf, int start, int len) {
079    return read(ByteBuffer.wrap(buf, start, len));
080  }
081
082  /**
083   * Deserializes a value from a byte array, creating a new object.
084   *
085   * @param buf byte array
086   * @return new object
087   */
088  public T read(byte[] buf) {
089    return read(buf, 0, buf.length);
090  }
091
092  /**
093   * Deserializes a value from a ByteBuffer, creating a new object.
094   *
095   * @param buf byte buffer
096   * @return new object
097   */
098  public T read(ByteBuffer buf) {
099    buf.order(ByteOrder.LITTLE_ENDIAN);
100    return m_struct.unpack(buf);
101  }
102
103  /**
104   * Deserializes a value from a byte array into a mutable object.
105   *
106   * @param out object (will be updated with deserialized contents)
107   * @param buf byte array
108   * @param start starting location within byte array
109   * @param len length of serialized data
110   * @throws UnsupportedOperationException if T is immutable
111   */
112  public void readInto(T out, byte[] buf, int start, int len) {
113    readInto(out, ByteBuffer.wrap(buf, start, len));
114  }
115
116  /**
117   * Deserializes a value from a byte array into a mutable object.
118   *
119   * @param out object (will be updated with deserialized contents)
120   * @param buf byte array
121   * @throws UnsupportedOperationException if T is immutable
122   */
123  public void readInto(T out, byte[] buf) {
124    readInto(out, buf, 0, buf.length);
125  }
126
127  /**
128   * Deserializes a value from a ByteBuffer into a mutable object.
129   *
130   * @param out object (will be updated with deserialized contents)
131   * @param buf byte buffer
132   * @throws UnsupportedOperationException if T is immutable
133   */
134  public void readInto(T out, ByteBuffer buf) {
135    m_struct.unpackInto(out, buf);
136  }
137
138  /**
139   * Serializes a collection of values to a ByteBuffer. The returned ByteBuffer is a direct byte
140   * buffer with the position set to the end of the serialized data.
141   *
142   * @param values values
143   * @return byte buffer
144   */
145  public ByteBuffer writeArray(Collection<T> values) {
146    m_buf.position(0);
147    if ((values.size() * m_structSize) > m_buf.capacity()) {
148      m_buf =
149          ByteBuffer.allocateDirect(values.size() * m_structSize * 2)
150              .order(ByteOrder.LITTLE_ENDIAN);
151    }
152    for (T v : values) {
153      m_struct.pack(m_buf, v);
154    }
155    return m_buf;
156  }
157
158  /**
159   * Serializes an array of values to a ByteBuffer. The returned ByteBuffer is a direct byte buffer
160   * with the position set to the end of the serialized data.
161   *
162   * @param values values
163   * @return byte buffer
164   */
165  public ByteBuffer writeArray(T[] values) {
166    m_buf.position(0);
167    if ((values.length * m_structSize) > m_buf.capacity()) {
168      m_buf =
169          ByteBuffer.allocateDirect(values.length * m_structSize * 2)
170              .order(ByteOrder.LITTLE_ENDIAN);
171    }
172    for (T v : values) {
173      m_struct.pack(m_buf, v);
174    }
175    return m_buf;
176  }
177
178  /**
179   * Deserializes an array of values from a byte array, creating an array of new objects.
180   *
181   * @param buf byte array
182   * @param start starting location within byte array
183   * @param len length of serialized data
184   * @return new object array
185   */
186  public T[] readArray(byte[] buf, int start, int len) {
187    return readArray(ByteBuffer.wrap(buf, start, len));
188  }
189
190  /**
191   * Deserializes an array of values from a byte array, creating an array of new objects.
192   *
193   * @param buf byte array
194   * @return new object array
195   */
196  public T[] readArray(byte[] buf) {
197    return readArray(buf, 0, buf.length);
198  }
199
200  /**
201   * Deserializes an array of values from a ByteBuffer, creating an array of new objects.
202   *
203   * @param buf byte buffer
204   * @return new object array
205   */
206  public T[] readArray(ByteBuffer buf) {
207    buf.order(ByteOrder.LITTLE_ENDIAN);
208    int len = buf.limit() - buf.position();
209    if ((len % m_structSize) != 0) {
210      throw new RuntimeException("buffer size not a multiple of struct size");
211    }
212    int nelem = len / m_structSize;
213    @SuppressWarnings("unchecked")
214    T[] arr = (T[]) Array.newInstance(m_struct.getTypeClass(), nelem);
215    for (int i = 0; i < nelem; i++) {
216      arr[i] = m_struct.unpack(buf);
217    }
218    return arr;
219  }
220
221  private ByteBuffer m_buf;
222  private final Struct<T> m_struct;
223  private final int m_structSize;
224}