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 edu.wpi.first.epilogue.CustomLoggerFor;
008import edu.wpi.first.epilogue.logging.errors.ErrorHandler;
009import edu.wpi.first.util.sendable.Sendable;
010import edu.wpi.first.util.sendable.SendableBuilder;
011import java.util.LinkedHashMap;
012import java.util.Map;
013
014/**
015 * Base class for class-specific generated loggers. Loggers are generated at compile time by the
016 * Epilogue annotation processor and are used at runtime for zero-overhead data logging. Users may
017 * also declare custom loggers, annotated with {@link CustomLoggerFor @CustomLoggerFor}, for
018 * Epilogue to pull in during compile time to use for logging third party types.
019 *
020 * @param <T> the type of data supported by the logger
021 */
022@SuppressWarnings("unused") // Used by generated subclasses
023public abstract class ClassSpecificLogger<T> {
024  private final Class<T> m_clazz;
025  // TODO: This will hold onto Sendables that are otherwise no longer referenced by a robot program.
026  //       Determine if that's a concern
027  // Linked hashmap to maintain insert order
028  private final Map<Sendable, SendableBuilder> m_sendables = new LinkedHashMap<>();
029
030  private boolean m_disabled = false;
031
032  /**
033   * Instantiates the logger.
034   *
035   * @param clazz the Java class of objects that can be logged
036   */
037  protected ClassSpecificLogger(Class<T> clazz) {
038    this.m_clazz = clazz;
039  }
040
041  /**
042   * Updates an object's fields in a data log.
043   *
044   * @param backend the backend to update
045   * @param object the object to update in the log
046   */
047  protected abstract void update(EpilogueBackend backend, T object);
048
049  /**
050   * Attempts to update the data log. Will do nothing if the logger is {@link #disable() disabled}.
051   *
052   * @param backend the backend to log data to
053   * @param object the data object to log
054   * @param errorHandler the handler to use if logging raised an exception
055   */
056  @SuppressWarnings("PMD.AvoidCatchingGenericException")
057  public final void tryUpdate(EpilogueBackend backend, T object, ErrorHandler errorHandler) {
058    if (m_disabled) {
059      return;
060    }
061
062    try {
063      update(backend, object);
064    } catch (Exception e) {
065      errorHandler.handle(e, this);
066    }
067  }
068
069  /**
070   * Checks if this logger has been disabled.
071   *
072   * @return true if this logger has been disabled by {@link #disable()}, false if not
073   */
074  public final boolean isDisabled() {
075    return m_disabled;
076  }
077
078  /** Disables this logger. Any log calls made while disabled will be ignored. */
079  public final void disable() {
080    m_disabled = true;
081  }
082
083  /** Reenables this logger after being disabled. Has no effect if the logger is already enabled. */
084  public final void reenable() {
085    m_disabled = false;
086  }
087
088  /**
089   * Gets the type of the data this logger accepts.
090   *
091   * @return the logged data type
092   */
093  public final Class<T> getLoggedType() {
094    return m_clazz;
095  }
096
097  /**
098   * Logs a sendable type.
099   *
100   * @param backend the backend to log data into
101   * @param sendable the sendable object to log
102   */
103  protected void logSendable(EpilogueBackend backend, Sendable sendable) {
104    if (sendable == null) {
105      return;
106    }
107
108    if (m_sendables.containsKey(sendable)) {
109      m_sendables.get(sendable).update();
110    } else {
111      var builder = new LogBackedSendableBuilder(backend);
112      sendable.initSendable(builder);
113      m_sendables.put(sendable, builder);
114      builder.update();
115    }
116  }
117}