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}