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.nio.ByteBuffer;
010
011/**
012 * Log struct-encoded values.
013 *
014 * @param <T> value class
015 */
016public final class StructLogEntry<T> extends DataLogEntry {
017  private StructLogEntry(
018      DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
019    super(log, name, struct.getTypeString(), metadata, timestamp);
020    m_buf = StructBuffer.create(struct);
021    m_immutable = struct.isImmutable();
022    m_cloneable = struct.isCloneable();
023    log.addSchema(struct, timestamp);
024  }
025
026  /**
027   * Creates a struct-encoded log entry.
028   *
029   * @param <T> value class (inferred from struct)
030   * @param log datalog
031   * @param name name of the entry
032   * @param struct struct serialization implementation
033   * @param metadata metadata
034   * @param timestamp entry creation timestamp (0=now)
035   * @return StructLogEntry
036   */
037  public static <T> StructLogEntry<T> create(
038      DataLog log, String name, Struct<T> struct, String metadata, long timestamp) {
039    return new StructLogEntry<>(log, name, struct, metadata, timestamp);
040  }
041
042  /**
043   * Creates a struct-encoded log entry.
044   *
045   * @param <T> value class (inferred from struct)
046   * @param log datalog
047   * @param name name of the entry
048   * @param struct struct serialization implementation
049   * @param metadata metadata
050   * @return StructLogEntry
051   */
052  public static <T> StructLogEntry<T> create(
053      DataLog log, String name, Struct<T> struct, String metadata) {
054    return create(log, name, struct, metadata, 0);
055  }
056
057  /**
058   * Creates a struct-encoded log entry.
059   *
060   * @param <T> value class (inferred from struct)
061   * @param log datalog
062   * @param name name of the entry
063   * @param struct struct serialization implementation
064   * @param timestamp entry creation timestamp (0=now)
065   * @return StructLogEntry
066   */
067  public static <T> StructLogEntry<T> create(
068      DataLog log, String name, Struct<T> struct, long timestamp) {
069    return create(log, name, struct, "", timestamp);
070  }
071
072  /**
073   * Creates a struct-encoded log entry.
074   *
075   * @param <T> value class (inferred from struct)
076   * @param log datalog
077   * @param name name of the entry
078   * @param struct struct serialization implementation
079   * @return StructLogEntry
080   */
081  public static <T> StructLogEntry<T> create(DataLog log, String name, Struct<T> struct) {
082    return create(log, name, struct, 0);
083  }
084
085  /**
086   * Appends a record to the log.
087   *
088   * @param value Value to record
089   * @param timestamp Time stamp (0 to indicate now)
090   */
091  public void append(T value, long timestamp) {
092    synchronized (m_buf) {
093      ByteBuffer bb = m_buf.write(value);
094      m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
095    }
096  }
097
098  /**
099   * Appends a record to the log.
100   *
101   * @param value Value to record
102   */
103  public void append(T value) {
104    append(value, 0);
105  }
106
107  /**
108   * Updates the last value and appends a record to the log if it has changed.
109   *
110   * <p>Note: the last value is local to this class instance; using update() with two instances
111   * pointing to the same underlying log entry name will likely result in unexpected results.
112   *
113   * @param value Value to record
114   * @param timestamp Time stamp (0 to indicate now)
115   */
116  public void update(T value, long timestamp) {
117    synchronized (m_buf) {
118      if (m_immutable || m_cloneable) {
119        if (value.equals(m_lastValue)) {
120          return;
121        }
122        try {
123          if (m_immutable) {
124            m_lastValue = value;
125          } else {
126            m_lastValue = m_buf.getStruct().clone(value);
127          }
128          ByteBuffer bb = m_buf.write(value);
129          m_log.appendRaw(m_entry, bb, 0, bb.position(), timestamp);
130          return;
131        } catch (CloneNotSupportedException e) {
132          // fall through
133        }
134      }
135      doUpdate(m_buf.write(value), timestamp);
136    }
137  }
138
139  /**
140   * Updates the last value and appends a record to the log if it has changed.
141   *
142   * <p>Note: the last value is local to this class instance; using update() with two instances
143   * pointing to the same underlying log entry name will likely result in unexpected results.
144   *
145   * @param value Value to record
146   */
147  public void update(T value) {
148    update(value, 0);
149  }
150
151  /**
152   * Gets whether there is a last value.
153   *
154   * <p>Note: the last value is local to this class instance and updated only with update(), not
155   * append().
156   *
157   * @return True if last value exists, false otherwise.
158   */
159  public boolean hasLastValue() {
160    synchronized (m_buf) {
161      if (m_immutable) {
162        return m_lastValue != null;
163      } else if (m_cloneable && m_lastValue != null) {
164        return true;
165      }
166      return m_lastValueBuf != null;
167    }
168  }
169
170  /**
171   * Gets the last value.
172   *
173   * <p>Note: the last value is local to this class instance and updated only with update(), not
174   * append().
175   *
176   * @return Last value, or null if none.
177   */
178  public T getLastValue() {
179    synchronized (m_buf) {
180      if (m_immutable) {
181        return m_lastValue;
182      } else if (m_cloneable && m_lastValue != null) {
183        try {
184          return m_buf.getStruct().clone(m_lastValue);
185        } catch (CloneNotSupportedException e) {
186          // fall through
187        }
188      }
189      if (m_lastValueBuf == null) {
190        return null;
191      }
192      T val = m_buf.read(m_lastValueBuf);
193      m_lastValueBuf.position(0);
194      return val;
195    }
196  }
197
198  private void doUpdate(ByteBuffer bb, long timestamp) {
199    int len = bb.position();
200    bb.limit(len);
201    bb.position(0);
202    if (m_lastValueBuf == null || !bb.equals(m_lastValueBuf)) {
203      // update last value
204      if (m_lastValueBuf == null || m_lastValueBuf.limit() < len) {
205        m_lastValueBuf = ByteBuffer.allocate(len);
206      }
207      bb.get(m_lastValueBuf.array(), 0, len);
208      bb.position(0);
209      m_lastValueBuf.limit(len);
210
211      // append to log
212      m_log.appendRaw(m_entry, bb, 0, len, timestamp);
213    }
214  }
215
216  private final StructBuffer<T> m_buf;
217  private ByteBuffer m_lastValueBuf;
218  private final boolean m_immutable;
219  private final boolean m_cloneable;
220  private T m_lastValue;
221}