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.epilogue.logging;
006
007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
008
009import edu.wpi.first.util.datalog.BooleanArrayLogEntry;
010import edu.wpi.first.util.datalog.BooleanLogEntry;
011import edu.wpi.first.util.datalog.DataLog;
012import edu.wpi.first.util.datalog.DataLogEntry;
013import edu.wpi.first.util.datalog.DoubleArrayLogEntry;
014import edu.wpi.first.util.datalog.DoubleLogEntry;
015import edu.wpi.first.util.datalog.FloatArrayLogEntry;
016import edu.wpi.first.util.datalog.FloatLogEntry;
017import edu.wpi.first.util.datalog.IntegerArrayLogEntry;
018import edu.wpi.first.util.datalog.IntegerLogEntry;
019import edu.wpi.first.util.datalog.RawLogEntry;
020import edu.wpi.first.util.datalog.StringArrayLogEntry;
021import edu.wpi.first.util.datalog.StringLogEntry;
022import edu.wpi.first.util.datalog.StructArrayLogEntry;
023import edu.wpi.first.util.datalog.StructLogEntry;
024import edu.wpi.first.util.struct.Struct;
025import java.util.HashMap;
026import java.util.Map;
027import java.util.function.BiFunction;
028
029/** A backend implementation that saves information to a WPILib {@link DataLog} file on disk. */
030public class FileBackend implements EpilogueBackend {
031  private final DataLog m_dataLog;
032  private final Map<String, DataLogEntry> m_entries = new HashMap<>();
033  private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
034
035  /**
036   * Creates a new file-based backend.
037   *
038   * @param dataLog the data log to save data to
039   */
040  public FileBackend(DataLog dataLog) {
041    this.m_dataLog = requireNonNullParam(dataLog, "dataLog", "FileBackend");
042  }
043
044  @Override
045  public EpilogueBackend getNested(String path) {
046    return m_subLoggers.computeIfAbsent(path, k -> new NestedBackend(k, this));
047  }
048
049  @SuppressWarnings("unchecked")
050  private <E extends DataLogEntry> E getEntry(
051      String identifier, BiFunction<DataLog, String, ? extends E> ctor) {
052    if (m_entries.get(identifier) != null) {
053      return (E) m_entries.get(identifier);
054    }
055
056    var entry = ctor.apply(m_dataLog, identifier);
057    m_entries.put(identifier, entry);
058    return entry;
059  }
060
061  @Override
062  public void log(String identifier, int value) {
063    getEntry(identifier, IntegerLogEntry::new).append(value);
064  }
065
066  @Override
067  public void log(String identifier, long value) {
068    getEntry(identifier, IntegerLogEntry::new).append(value);
069  }
070
071  @Override
072  public void log(String identifier, float value) {
073    getEntry(identifier, FloatLogEntry::new).append(value);
074  }
075
076  @Override
077  public void log(String identifier, double value) {
078    getEntry(identifier, DoubleLogEntry::new).append(value);
079  }
080
081  @Override
082  public void log(String identifier, boolean value) {
083    getEntry(identifier, BooleanLogEntry::new).append(value);
084  }
085
086  @Override
087  public void log(String identifier, byte[] value) {
088    getEntry(identifier, RawLogEntry::new).append(value);
089  }
090
091  @Override
092  @SuppressWarnings("PMD.UnnecessaryCastRule")
093  public void log(String identifier, int[] value) {
094    long[] widened = new long[value.length];
095    for (int i = 0; i < value.length; i++) {
096      widened[i] = (long) value[i];
097    }
098    getEntry(identifier, IntegerArrayLogEntry::new).append(widened);
099  }
100
101  @Override
102  public void log(String identifier, long[] value) {
103    getEntry(identifier, IntegerArrayLogEntry::new).append(value);
104  }
105
106  @Override
107  public void log(String identifier, float[] value) {
108    getEntry(identifier, FloatArrayLogEntry::new).append(value);
109  }
110
111  @Override
112  public void log(String identifier, double[] value) {
113    getEntry(identifier, DoubleArrayLogEntry::new).append(value);
114  }
115
116  @Override
117  public void log(String identifier, boolean[] value) {
118    getEntry(identifier, BooleanArrayLogEntry::new).append(value);
119  }
120
121  @Override
122  public void log(String identifier, String value) {
123    getEntry(identifier, StringLogEntry::new).append(value);
124  }
125
126  @Override
127  public void log(String identifier, String[] value) {
128    getEntry(identifier, StringArrayLogEntry::new).append(value);
129  }
130
131  @Override
132  @SuppressWarnings("unchecked")
133  public <S> void log(String identifier, S value, Struct<S> struct) {
134    m_dataLog.addSchema(struct);
135    getEntry(identifier, (log, k) -> StructLogEntry.create(log, k, struct)).append(value);
136  }
137
138  @Override
139  @SuppressWarnings("unchecked")
140  public <S> void log(String identifier, S[] value, Struct<S> struct) {
141    m_dataLog.addSchema(struct);
142    getEntry(identifier, (log, k) -> StructArrayLogEntry.create(log, k, struct)).append(value);
143  }
144}