001/************************ PROJECT ZIXI ************************/ 002/* Copyright (c) 2026 StuyPulse Robotics. All rights reserved.*/ 003/* This work is licensed under the terms of the MIT license. */ 004/**************************************************************/ 005package com.stuypulse.robot.util.talonfx.logged; 006 007import org.wpilib.units.Measure; 008import org.wpilib.util.Color; 009import org.wpilib.util.WPISerializable; 010import org.wpilib.util.struct.StructSerializable; 011 012import java.util.Arrays; 013import java.util.Objects; 014import org.littletonrobotics.junction.LogTable; 015 016/** 017 * 018 * 019 * <h2>Log Table Utility</h2> 020 * 021 * <p>Utility methods for reading and writing dynamically typed values to and from a {@code 022 * LogTable}. 023 * 024 * <p>This centralizes type handling without having to duplicate the same if else logic in multiple 025 * places. 026 * 027 * @author Faizaan (https://github.com/Faizaan-J) 028 */ 029public final class LogTableUtil { 030 private LogTableUtil() {} 031 032 /** 033 * Puts a value into a {@code LogTable} with the given key, automatically handling the type of 034 * the value. 035 * 036 * @param table the {@code LogTable} to put the value into 037 * @param key The key where the value will be stored. 038 * @param value The value to store in the {@code LogTable}. If it's not a supported type, it 039 * will be converted to a string. 040 */ 041 public static void put(LogTable table, String key, Object value) { 042 Objects.requireNonNull(table, "LogTable cannot be null"); 043 Objects.requireNonNull(key, "Logging key cannot be null"); 044 switch (value) { 045 case Object _ when value.getClass().isArray() -> putArray(table, key, value); 046 case Measure<?> measure -> table.putMeasure(key, measure); 047 case WPISerializable serializable -> table.put(key, serializable); 048 case Color color -> table.put(key, color); 049 case Boolean b -> table.put(key, b); 050 case Double d -> table.put(key, d); 051 case Integer i -> table.put(key, i); 052 case Long l -> table.put(key, l); 053 case String s -> table.put(key, s); 054 case Enum<?> enumerator -> putEnum(table, key, enumerator); 055 case Record record -> table.put(key, record); 056 case null -> table.put(key, "null"); 057 default -> table.put(key, value.toString()); 058 } 059 } 060 061 /** 062 * Puts an array value into a {@code LogTable}, dispatching to the corresponding array overload. 063 * Falls back to a {@code String[]} for element types with no array overload such as a {@code 064 * Measure} or boxed wrapper types. 065 */ 066 private static void putArray(LogTable table, String key, Object array) { 067 switch (array) { 068 case boolean[] bools -> table.put(key, bools); 069 case int[] ints -> table.put(key, ints); 070 case long[] longs -> table.put(key, longs); 071 case float[] floats -> table.put(key, floats); 072 case double[] doubles -> table.put(key, doubles); 073 case String[] strings -> table.put(key, strings); 074 case byte[] bytes -> table.put(key, bytes); 075 case Object enums when enums instanceof Enum<?>[] -> 076 putEnumArray(table, key, (Enum<?>[]) enums); 077 case StructSerializable[] serializables -> table.put(key, serializables); 078 case Record[] records -> table.put(key, records); 079 case Object[] objects -> 080 table.put( 081 key, 082 Arrays.stream(objects).map(String::valueOf).toArray(String[]::new)); 083 case null, default -> 084 throw new IllegalArgumentException( 085 "Unsupported or invalid array type: " 086 + (array == null ? "null" : array.getClass().getName())); 087 } 088 } 089 090 /** 091 * Gets a value from a {@code LogTable} with the given key, automatically handling the type of 092 * the value based on the provided default value. 093 * 094 * @param <T> The type of the value. 095 * @param table The {@code LogTable} to get the value from. 096 * @param key The key where the valid is stored. 097 * @param defaultValue The default value to return if the key does not exist in the {@code 098 * LogTable} . 099 * @return The value from the {@code LogTable} if it exists, otherwise the default value. 100 */ 101 @SuppressWarnings({"unchecked"}) 102 public static <T> T get(LogTable table, String key, T defaultValue) { 103 Objects.requireNonNull(table, "LogTable cannot be null"); 104 Objects.requireNonNull(key, "Logging key cannot be null"); 105 return (T) 106 switch (defaultValue) { 107 case Object _ when defaultValue.getClass().isArray() -> 108 getArray(table, key, defaultValue); 109 case Measure<?> measure -> table.getMeasure(key, measure); 110 case WPISerializable serializable -> table.get(key, serializable); 111 case Color color -> table.get(key, color); 112 case Boolean b -> Boolean.valueOf(table.get(key, b)); 113 case Double d -> Double.valueOf(table.get(key, d)); 114 case Integer i -> Integer.valueOf(table.get(key, i)); 115 case Long l -> Long.valueOf(table.get(key, l)); 116 case String s -> table.get(key, s); 117 case Object enumerator when enumerator instanceof Enum<?> -> 118 getEnum(table, key, (Enum<?>) enumerator); 119 case Record record -> table.get(key, record); 120 case null, default -> defaultValue; 121 }; 122 } 123 124 /** 125 * Gets an array value from a {@code LogTable}, dispatching to the corresponding array overload. 126 * Types with no array overload such as {@code Measure} or boxed wrapper types aren't 127 * replayable, so the default array is returned back. 128 */ 129 private static Object getArray(LogTable table, String key, Object defaultArray) { 130 return switch (defaultArray) { 131 case boolean[] bools -> table.get(key, bools); 132 case int[] ints -> table.get(key, ints); 133 case long[] longs -> table.get(key, longs); 134 case float[] floats -> table.get(key, floats); 135 case double[] doubles -> table.get(key, doubles); 136 case String[] strings -> table.get(key, strings); 137 case byte[] bytes -> table.get(key, bytes); 138 case Object enums when enums instanceof Enum<?>[] -> 139 getEnumArray(table, key, (Enum<?>[]) enums); 140 case StructSerializable[] serializables -> table.get(key, serializables); 141 case Record[] records -> table.get(key, records); 142 case Object[] objects -> 143 table.get( 144 key, 145 Arrays.stream(objects).map(String::valueOf).toArray(String[]::new)); 146 case null, default -> defaultArray; 147 }; 148 } 149 150 @SuppressWarnings("unchecked") 151 private static <E extends Enum<E>> void putEnum(LogTable table, String key, Enum<?> value) { 152 table.put(key, (E) value); 153 } 154 155 @SuppressWarnings("unchecked") 156 private static <E extends Enum<E>> E getEnum(LogTable table, String key, Enum<?> defaultValue) { 157 return table.get(key, (E) defaultValue); 158 } 159 160 @SuppressWarnings("unchecked") 161 private static <E extends Enum<E>> void putEnumArray( 162 LogTable table, String key, Enum<?>[] value) { 163 table.put(key, (E[]) value); 164 } 165 166 @SuppressWarnings("unchecked") 167 private static <E extends Enum<E>> E[] getEnumArray( 168 LogTable table, String key, Enum<?>[] defaultValue) { 169 return table.get(key, (E[]) defaultValue); 170 } 171 172 /** 173 * Returns true if the value is a supported type for logging, false otherwise. 174 * 175 * @param value The value to check for logging support. 176 * @return true if the value is a supported type for logging, false otherwise. 177 */ 178 public static boolean isSupportedType(Object value) { 179 if (value == null) { 180 return false; 181 } 182 183 if (value.getClass().isArray()) { 184 return isSupportedArrayType(value); 185 } 186 187 return switch (value) { 188 case Measure<?> _ -> true; 189 case WPISerializable _ -> true; 190 case Color _ -> true; 191 case Boolean _ -> true; 192 case Double _ -> true; 193 case Integer _ -> true; 194 case Long _ -> true; 195 case String _ -> true; 196 case Enum<?> _ -> true; 197 case Record _ -> true; 198 case null, default -> false; 199 }; 200 } 201 202 private static boolean isSupportedArrayType(Object value) { 203 return switch (value) { 204 case boolean[] _ -> true; 205 case int[] _ -> true; 206 case long[] _ -> true; 207 case float[] _ -> true; 208 case double[] _ -> true; 209 case byte[] _ -> true; 210 case String[] _ -> true; 211 case Object _ when value instanceof Enum<?>[] _ -> true; 212 case StructSerializable[] _ -> true; 213 case Object[] array -> array.getClass().getComponentType().isRecord(); 214 case null, default -> false; 215 }; 216 } 217}