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  @SuppressWarnings("PMD.RedundantFieldInitializer")
031  private boolean m_disabled = false;
032
033  /**
034   * Instantiates the logger.
035   *
036   * @param clazz the Java class of objects that can be logged
037   */
038  protected ClassSpecificLogger(Class<T> clazz) {
039    this.m_clazz = clazz;
040  }
041
042  /**
043   * Updates an object's fields in a data log.
044   *
045   * @param backend the backend to update
046   * @param object the object to update in the log
047   */
048  protected abstract void update(EpilogueBackend backend, T object);
049
050  /**
051   * Attempts to update the data log. Will do nothing if the logger is {@link #disable() disabled}.
052   *
053   * @param backend the backend to log data to
054   * @param object the data object to log
055   * @param errorHandler the handler to use if logging raised an exception
056   */
057  @SuppressWarnings("PMD.AvoidCatchingGenericException")
058  public final void tryUpdate(EpilogueBackend backend, T object, ErrorHandler errorHandler) {
059    if (m_disabled) {
060      return;
061    }
062
063    try {
064      update(backend, object);
065    } catch (Exception e) {
066      errorHandler.handle(e, this);
067    }
068  }
069
070  /**
071   * Checks if this logger has been disabled.
072   *
073   * @return true if this logger has been disabled by {@link #disable()}, false if not
074   */
075  public final boolean isDisabled() {
076    return m_disabled;
077  }
078
079  /** Disables this logger. Any log calls made while disabled will be ignored. */
080  public final void disable() {
081    m_disabled = true;
082  }
083
084  /** Reenables this logger after being disabled. Has no effect if the logger is already enabled. */
085  public final void reenable() {
086    m_disabled = false;
087  }
088
089  /**
090   * Gets the type of the data this logger accepts.
091   *
092   * @return the logged data type
093   */
094  public final Class<T> getLoggedType() {
095    return m_clazz;
096  }
097
098  /**
099   * Logs a sendable type.
100   *
101   * @param backend the backend to log data into
102   * @param sendable the sendable object to log
103   */
104  protected void logSendable(EpilogueBackend backend, Sendable sendable) {
105    if (sendable == null) {
106      return;
107    }
108
109    var builder =
110        m_sendables.computeIfAbsent(
111            sendable,
112            s -> {
113              var b = new LogBackedSendableBuilder(backend);
114              s.initSendable(b);
115              return b;
116            });
117    builder.update();
118  }
119}