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.datalog;
006
007import edu.wpi.first.util.struct.Struct;
008import edu.wpi.first.util.struct.StructBuffer;
009import java.lang.reflect.Array;
010import java.nio.ByteBuffer;
011import java.util.Arrays;
012import java.util.Collection;
013
014/**
015 * Log struct-encoded array values.
016 *
017 * @param <T> value class
018 */
019public final class StructArrayLogEntry<T> extends DataLogEntry {
020  private StructArrayLogEntry(
021      DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
022    super(log, name, struct.getTypeString() + "[]", metadata, timestamp);
023    m_buf = StructBuffer.create(struct);
024    m_immutable = struct.isImmutable();
025    m_cloneable = struct.isCloneable();
026    log.addSchema(struct, timestamp);
027  }
028
029  /**
030   * Creates a struct-encoded array log entry.
031   *
032   * @param <T> value class (inferred from struct)
033   * @param log datalog
034   * @param name name of the entry
035   * @param struct struct serialization implementation
036   * @param metadata metadata
037   * @param timestamp entry creation timestamp (0=now)
038   * @return StructArrayLogEntry
039   */
040  public static <T> StructArrayLogEntry<T> create(
041      DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
042    return new StructArrayLogEntry<>(log, name, struct, metadata, timestamp);
043  }
044
045  /**
046   * Creates a struct-encoded array log entry.
047   *
048   * @param <T> value class (inferred from struct)
049   * @param log datalog
050   * @param name name of the entry
051   * @param struct struct serialization implementation
052   * @param metadata metadata
053   * @return StructArrayLogEntry
054   */
055  public static <T> StructArrayLogEntry<T> create(
056      DataLog log, String name, Struct<T> struct, String metadata) {
057    return create(log, name, struct, metadata, 0);
058  }
059
060  /**
061   * Creates a struct-encoded array log entry.
062   *
063   * @param <T> value class (inferred from struct)
064   * @param log datalog
065   * @param name name of the entry
066   * @param struct struct serialization implementation
067   * @param timestamp entry creation timestamp (0=now)
068   * @return StructArrayLogEntry
069   */
070  public static <T> StructArrayLogEntry<T> create(
071      DataLog log, String name, Struct<T> struct, long timestamp) {
072    return create(log, name, struct, "", timestamp);
073  }
074
075  /**
076   * Creates a struct-encoded array log entry.
077   *
078   * @param <T> value class (inferred from struct)
079   * @param log datalog
080   * @param name name of the entry
081   * @param struct struct serialization implementation
082   * @return StructArrayLogEntry
083   */
084  public static <T> StructArrayLogEntry<T> create(DataLog log, String name, Struct<T> struct) {
085    return create(log, name, struct, 0);
086  }
087
088  /**
089   * Ensures sufficient buffer space is available for the given number of elements.
090   *
091   * @param nelem number of elements
092   */
093  public void reserve(int nelem) {
094    synchronized (m_buf) {
095      m_buf.reserve(nelem);
096    }
097  }
098
099  /**
100   * Appends a record to the log.
101   *
102   * @param value Value to record
103   * @param timestamp Time stamp (0 to indicate now)
104   */
105  public void append(T[] value, long timestamp) {
106    synchronized (m_buf) {
107      ByteBuffer bb = m_buf.writeArray(value);
108      m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
109    }
110  }
111
112  /**
113   * Appends a record to the log.
114   *
115   * @param value Value to record
116   */
117  public void append(T[] value) {
118    append(value, 0);
119  }
120
121  /**
122   * Appends a record to the log.
123   *
124   * @param value Value to record
125   * @param timestamp Time stamp (0 to indicate now)
126   */
127  public void append(Collection<T> value, long timestamp) {
128    synchronized (m_buf) {
129      ByteBuffer bb = m_buf.writeArray(value);
130      m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
131    }
132  }
133
134  /**
135   * Appends a record to the log.
136   *
137   * @param value Value to record
138   */
139  public void append(Collection<T> value) {
140    append(value, 0);
141  }
142
143  /**
144   * Updates the last value and appends a record to the log if it has changed.
145   *
146   * <p>Note: the last value is local to this class instance; using update() with two instances
147   * pointing to the same underlying log entry name will likely result in unexpected results.
148   *
149   * @param value Value to record
150   * @param timestamp Time stamp (0 to indicate now)
151   */
152  public void update(T[] value, long timestamp) {
153    synchronized (m_buf) {
154      if (m_immutable || m_cloneable) {
155        if (m_lastValue != null
156            && m_lastValueLen == value.length
157            && Arrays.equals(m_lastValue, 0, value.length, value, 0, value.length)) {
158          return;
159        }
160        try {
161          copyToLast(value);
162          ByteBuffer bb = m_buf.writeArray(value);
163          m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
164          return;
165        } catch (CloneNotSupportedException e) {
166          // fall through
167        }
168      }
169      doUpdate(m_buf.writeArray(value), timestamp);
170    }
171  }
172
173  /**
174   * Updates the last value and appends a record to the log if it has changed.
175   *
176   * <p>Note: the last value is local to this class instance; using update() with two instances
177   * pointing to the same underlying log entry name will likely result in unexpected results.
178   *
179   * @param value Value to record
180   */
181  public void update(T[] value) {
182    update(value, 0);
183  }
184
185  /**
186   * Updates the last value and appends a record to the log if it has changed.
187   *
188   * <p>Note: the last value is local to this class instance; using update() with two instances
189   * pointing to the same underlying log entry name will likely result in unexpected results.
190   *
191   * @param value Value to record
192   * @param timestamp Time stamp (0 to indicate now)
193   */
194  public void update(Collection<T> value, long timestamp) {
195    synchronized (m_buf) {
196      if (m_immutable || m_cloneable) {
197        if (equalsLast(value)) {
198          return;
199        }
200        try {
201          copyToLast(value);
202          ByteBuffer bb = m_buf.writeArray(value);
203          m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
204          return;
205        } catch (CloneNotSupportedException e) {
206          // fall through
207        }
208      }
209      doUpdate(m_buf.writeArray(value), timestamp);
210    }
211  }
212
213  /**
214   * Updates the last value and appends a record to the log if it has changed.
215   *
216   * <p>Note: the last value is local to this class instance; using update() with two instances
217   * pointing to the same underlying log entry name will likely result in unexpected results.
218   *
219   * @param value Value to record
220   */
221  public void update(Collection<T> value) {
222    update(value, 0);
223  }
224
225  /**
226   * Gets whether there is a last value.
227   *
228   * <p>Note: the last value is local to this class instance and updated only with update(), not
229   * append().
230   *
231   * @return True if last value exists, false otherwise.
232   */
233  public boolean hasLastValue() {
234    synchronized (m_buf) {
235      if (m_immutable) {
236        return m_lastValue != null;
237      } else if (m_cloneable && m_lastValue != null) {
238        return true;
239      }
240      return m_lastValueBuf != null;
241    }
242  }
243
244  /**
245   * Gets the last value.
246   *
247   * <p>Note: the last value is local to this class instance and updated only with update(), not
248   * append().
249   *
250   * @return Last value, or null if none.
251   */
252  @SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
253  public T[] getLastValue() {
254    synchronized (m_buf) {
255      if (m_immutable) {
256        if (m_lastValue == null) {
257          return null;
258        }
259        return Arrays.copyOf(m_lastValue, m_lastValueLen);
260      } else if (m_cloneable && m_lastValue != null) {
261        try {
262          return cloneArray(m_lastValue, m_lastValueLen);
263        } catch (CloneNotSupportedException e) {
264          // fall through
265        }
266      }
267      if (m_lastValueBuf == null) {
268        return null;
269      }
270      T[] val = m_buf.readArray(m_lastValueBuf);
271      m_lastValueBuf.position(0);
272      return val;
273    }
274  }
275
276  private void doUpdate(ByteBuffer bb, long timestamp) {
277    int len = bb.position();
278    bb.limit(len);
279    bb.position(0);
280    if (m_lastValueBuf == null || !bb.equals(m_lastValueBuf)) {
281      // update last value
282      if (m_lastValueBuf == null || m_lastValueBuf.limit() < len) {
283        m_lastValueBuf = ByteBuffer.allocate(len);
284      }
285      bb.get(m_lastValueBuf.array(), 0, len);
286      bb.position(0);
287      m_lastValueBuf.limit(len);
288
289      // append to log
290      m_log.appendRaw(m_entry, bb, 0, len, timestamp);
291    }
292  }
293
294  private boolean equalsLast(Collection<T> arr) {
295    if (m_lastValue == null) {
296      return false;
297    }
298    if (m_lastValueLen != arr.size()) {
299      return false;
300    }
301    int i = 0;
302    for (T elem : arr) {
303      if (!m_lastValue[i].equals(elem)) {
304        return false;
305      }
306      i++;
307    }
308    return true;
309  }
310
311  private T[] cloneArray(T[] in, int len) throws CloneNotSupportedException {
312    Struct<T> s = m_buf.getStruct();
313    @SuppressWarnings("unchecked")
314    T[] arr = (T[]) Array.newInstance(s.getTypeClass(), len);
315    for (int i = 0; i < len; i++) {
316      arr[i] = s.clone(in[i]);
317    }
318    return arr;
319  }
320
321  private T[] cloneArray(Collection<T> in) throws CloneNotSupportedException {
322    Struct<T> s = m_buf.getStruct();
323    @SuppressWarnings("unchecked")
324    T[] arr = (T[]) Array.newInstance(s.getTypeClass(), in.size());
325    int i = 0;
326    for (T elem : in) {
327      arr[i++] = s.clone(elem);
328    }
329    return arr;
330  }
331
332  private void copyToLast(T[] value) throws CloneNotSupportedException {
333    if (m_lastValue == null || m_lastValue.length < value.length) {
334      if (m_immutable) {
335        m_lastValue = Arrays.copyOf(value, value.length);
336      } else {
337        m_lastValue = cloneArray(value, value.length);
338      }
339    } else {
340      if (m_immutable) {
341        System.arraycopy(value, 0, m_lastValue, 0, value.length);
342      } else {
343        Struct<T> s = m_buf.getStruct();
344        for (int i = 0; i < value.length; i++) {
345          m_lastValue[i] = s.clone(value[i]);
346        }
347      }
348    }
349    m_lastValueLen = value.length;
350  }
351
352  private void copyToLast(Collection<T> value) throws CloneNotSupportedException {
353    if (m_lastValue == null || m_lastValue.length < value.size()) {
354      if (m_immutable) {
355        Struct<T> s = m_buf.getStruct();
356        @SuppressWarnings("unchecked")
357        T[] arr = (T[]) Array.newInstance(s.getTypeClass(), value.size());
358        int i = 0;
359        for (T elem : value) {
360          arr[i++] = elem;
361        }
362      } else {
363        m_lastValue = cloneArray(value);
364      }
365    } else {
366      Struct<T> s = m_buf.getStruct();
367      int i = 0;
368      for (T elem : value) {
369        m_lastValue[i++] = m_immutable ? elem : s.clone(elem);
370      }
371    }
372    m_lastValueLen = value.size();
373  }
374
375  private final StructBuffer<T> m_buf;
376  private ByteBuffer m_lastValueBuf;
377  private final boolean m_immutable;
378  private final boolean m_cloneable;
379  private T[] m_lastValue;
380  private int m_lastValueLen;
381}