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}