mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
Merge branch 'main' into 2027
This commit is contained in:
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.PrimitiveType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
@@ -52,7 +53,7 @@ public class ArrayHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
var dataType = dataType(element);
|
||||
|
||||
// known to be an array type (assuming isLoggable is checked first); this is a safe cast
|
||||
@@ -63,13 +64,17 @@ public class ArrayHandler extends ElementHandler {
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element)
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ", "
|
||||
+ m_structHandler.structAccess(componentType)
|
||||
+ ")";
|
||||
} else {
|
||||
// Primitive or string array
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
@@ -38,7 +39,7 @@ public class CollectionHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
var dataType = dataType(element);
|
||||
var componentType = ((DeclaredType) dataType).getTypeArguments().get(0);
|
||||
|
||||
@@ -46,12 +47,16 @@ public class CollectionHandler extends ElementHandler {
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element)
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ", "
|
||||
+ m_structHandler.structAccess(componentType)
|
||||
+ ")";
|
||||
} else {
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
import java.util.Map;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class ConfiguredLoggerHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
var dataType = dataType(element);
|
||||
var loggerType =
|
||||
m_customLoggers.entrySet().stream()
|
||||
@@ -44,7 +45,7 @@ public class ConfiguredLoggerHandler extends ElementHandler {
|
||||
+ ".tryUpdate(backend.getNested(\""
|
||||
+ loggedName(element)
|
||||
+ "\"), "
|
||||
+ elementAccess(element)
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ", Epilogue.getConfig().errorHandler)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +117,9 @@ public abstract class ElementHandler {
|
||||
* @param element the element to generate the access for
|
||||
* @return the generated access snippet
|
||||
*/
|
||||
public String elementAccess(Element element) {
|
||||
public String elementAccess(Element element, TypeElement loggedClass) {
|
||||
if (element instanceof VariableElement field) {
|
||||
return fieldAccess(field);
|
||||
return fieldAccess(field, loggedClass);
|
||||
} else if (element instanceof ExecutableElement method) {
|
||||
return methodAccess(method);
|
||||
} else {
|
||||
@@ -127,8 +127,20 @@ public abstract class ElementHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static String fieldAccess(VariableElement field) {
|
||||
if (!field.getModifiers().contains(Modifier.PUBLIC)) {
|
||||
private static String fieldAccess(VariableElement field, TypeElement loggedClass) {
|
||||
var mods = field.getModifiers();
|
||||
|
||||
// To be directly accessible, the field needs to be:
|
||||
// - public; or
|
||||
// - protected or package-private, and declared by a superclass in the same package
|
||||
// However, we can't cleanly access package information, so we'll always emit a VarHandle
|
||||
// for any field declared in a superclass unless it's public and we know we can read it.
|
||||
boolean isVarHandle =
|
||||
field.getEnclosingElement().equals(loggedClass)
|
||||
? mods.contains(Modifier.PRIVATE)
|
||||
: !mods.contains(Modifier.PUBLIC);
|
||||
|
||||
if (isVarHandle) {
|
||||
// ((com.example.Foo) $fooField.get(object))
|
||||
// Extra parentheses so cast evaluates before appended methods
|
||||
// (e.g. when appending .getAsDouble())
|
||||
@@ -136,7 +148,7 @@ public abstract class ElementHandler {
|
||||
if (type.getKind() == TypeKind.TYPEVAR) {
|
||||
type = ((TypeVariable) type).getUpperBound();
|
||||
}
|
||||
return "((" + type.toString() + ") $" + field.getSimpleName() + ".get(object))";
|
||||
return "((" + type.toString() + ") " + LoggerGenerator.varHandleName(field) + ".get(object))";
|
||||
} else {
|
||||
// object.fooField
|
||||
return "object." + field.getSimpleName();
|
||||
@@ -171,5 +183,5 @@ public abstract class ElementHandler {
|
||||
* @param element the field or method element to generate the logger call for
|
||||
* @return the generated log invocation
|
||||
*/
|
||||
public abstract String logInvocation(Element element);
|
||||
public abstract String logInvocation(Element element, TypeElement loggedClass);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public class EnumHandler extends ElementHandler {
|
||||
@@ -27,7 +28,11 @@ public class EnumHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class LoggableHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
TypeMirror dataType = dataType(element);
|
||||
var declaredType =
|
||||
m_processingEnv
|
||||
@@ -61,7 +61,7 @@ public class LoggableHandler extends ElementHandler {
|
||||
|
||||
// If there are no known loggable subtypes, return just the single logger call
|
||||
if (size == 1) {
|
||||
return generateLoggerCall(element, declaredType, elementAccess(element));
|
||||
return generateLoggerCall(element, declaredType, elementAccess(element, loggedClass));
|
||||
}
|
||||
|
||||
// Otherwise, generate an if-else chain to compare the element with its known loggable subtypes
|
||||
@@ -73,7 +73,7 @@ public class LoggableHandler extends ElementHandler {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// Cache the value in a variable so it's only read once
|
||||
builder.append("var %s = %s;\n".formatted(varName, elementAccess(element)));
|
||||
builder.append("var %s = %s;\n".formatted(varName, elementAccess(element, loggedClass)));
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
TypeElement type = loggableSubtypes.get(i);
|
||||
|
||||
@@ -18,9 +18,11 @@ import java.io.PrintWriter;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -185,7 +187,21 @@ public class LoggerGenerator {
|
||||
var loggerFile = m_processingEnv.getFiler().createSourceFile(loggerClassName);
|
||||
|
||||
var varHandleFields =
|
||||
loggableFields.stream().filter(e -> !e.getModifiers().contains(Modifier.PUBLIC)).toList();
|
||||
loggableFields.stream()
|
||||
.filter(
|
||||
e -> {
|
||||
if (e.getEnclosingElement().equals(clazz)) {
|
||||
// The generated logger is in the same package as the logged class, so the
|
||||
// only fields it can't read are private ones.
|
||||
return e.getModifiers().contains(Modifier.PRIVATE);
|
||||
} else {
|
||||
// Logging from a superclass. Can only read public fields, unless the superclass
|
||||
// is in the same package, in which case protected and package-private fields
|
||||
// are also readable.
|
||||
return !e.getModifiers().contains(Modifier.PUBLIC);
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
boolean requiresVarHandles = !varHandleFields.isEmpty();
|
||||
|
||||
try (var out = new PrintWriter(loggerFile.openWriter())) {
|
||||
@@ -214,41 +230,67 @@ public class LoggerGenerator {
|
||||
+ "> {");
|
||||
|
||||
if (requiresVarHandles) {
|
||||
for (var varHandleField : varHandleFields) {
|
||||
for (var privateField : varHandleFields) {
|
||||
// This field needs a VarHandle to access.
|
||||
// Cache it in the class to avoid lookups
|
||||
out.println(" private static final VarHandle $" + varHandleField.getSimpleName() + ";");
|
||||
out.printf(
|
||||
" // Accesses private or superclass field %s.%s%n",
|
||||
privateField.getEnclosingElement(), privateField.getSimpleName());
|
||||
out.printf(" private static final VarHandle %s;%n", varHandleName(privateField));
|
||||
}
|
||||
out.println();
|
||||
}
|
||||
|
||||
var classReference = simpleClassName + ".class";
|
||||
|
||||
// Static initializer block to load VarHandles and reflection fields
|
||||
if (requiresVarHandles) {
|
||||
out.println(" static {");
|
||||
out.println(" try {");
|
||||
out.println(
|
||||
" var lookup = MethodHandles.privateLookupIn("
|
||||
+ classReference
|
||||
+ ", MethodHandles.lookup());");
|
||||
|
||||
for (var varHandleField : varHandleFields) {
|
||||
var fieldName = varHandleField.getSimpleName();
|
||||
out.println(
|
||||
" $"
|
||||
+ fieldName
|
||||
+ " = lookup.findVarHandle("
|
||||
+ classReference
|
||||
+ ", \""
|
||||
+ fieldName
|
||||
+ "\", "
|
||||
+ m_processingEnv.getTypeUtils().erasure(varHandleField.asType())
|
||||
+ ".class);");
|
||||
}
|
||||
out.println(" try {");
|
||||
|
||||
out.println(" var rootLookup = MethodHandles.lookup();");
|
||||
|
||||
// Group private fields by class, then generate a private lookup for each class
|
||||
// and a VarHandle for each field using that lookup. Sorting and then collecting into a
|
||||
// LinkedHashMap gives deterministic output ordering (mostly for tests, which check exact
|
||||
// file contents, but also results in less churn when regenerating files for users who like
|
||||
// to read the generated logger classes).
|
||||
//
|
||||
// This lets us read private fields from superclasses.
|
||||
Map<Element, List<VariableElement>> privateFieldsByClass =
|
||||
varHandleFields.stream()
|
||||
.sorted(Comparator.comparing(e -> e.getSimpleName().toString()))
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
VariableElement::getEnclosingElement,
|
||||
LinkedHashMap::new,
|
||||
Collectors.toList()));
|
||||
|
||||
privateFieldsByClass.forEach(
|
||||
(enclosingClass, fields) -> {
|
||||
String className = enclosingClass.toString();
|
||||
String lookupName = "lookup$$" + className.replace(".", "_");
|
||||
out.printf(
|
||||
" var %s = MethodHandles.privateLookupIn(%s.class, rootLookup);%n",
|
||||
lookupName, className);
|
||||
|
||||
for (var field : fields) {
|
||||
var fieldname = field.getSimpleName();
|
||||
out.printf(
|
||||
" %s = %s.findVarHandle(%s.class, \"%s\", %s.class);%n",
|
||||
varHandleName(field),
|
||||
lookupName,
|
||||
className,
|
||||
fieldname,
|
||||
m_processingEnv.getTypeUtils().erasure(field.asType()));
|
||||
}
|
||||
});
|
||||
|
||||
out.println(" } catch (ReflectiveOperationException e) {");
|
||||
out.println(
|
||||
" throw new RuntimeException("
|
||||
+ "\"[EPILOGUE] Could not load private fields for logging!\", e);");
|
||||
out.println(" }");
|
||||
|
||||
out.println(" }");
|
||||
out.println();
|
||||
}
|
||||
@@ -300,7 +342,7 @@ public class LoggerGenerator {
|
||||
// to be logged. For example, the sendable handler consumes all sendable types
|
||||
// but does not log commands or subsystems, to prevent excessive warnings about
|
||||
// unloggable commands.
|
||||
var logInvocation = h.logInvocation(loggableElement);
|
||||
var logInvocation = h.logInvocation(loggableElement, clazz);
|
||||
if (logInvocation != null) {
|
||||
out.println(logInvocation.indent(6).stripTrailing() + ";");
|
||||
}
|
||||
@@ -315,6 +357,18 @@ public class LoggerGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the name of a VarHandle for access to the given field. The VarHandle variable's name
|
||||
* is guaranteed to be unique.
|
||||
*
|
||||
* @param field The field to generate a VarHandle for
|
||||
* @return The name of the generated VarHandle variable
|
||||
*/
|
||||
public static String varHandleName(VariableElement field) {
|
||||
return "$%s_%s"
|
||||
.formatted(field.getEnclosingElement().toString().replace(".", "_"), field.getSimpleName());
|
||||
}
|
||||
|
||||
private void collectLoggables(
|
||||
TypeElement clazz, List<VariableElement> fields, List<ExecutableElement> methods) {
|
||||
var config = clazz.getAnnotation(Logged.class);
|
||||
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public class MeasureHandler extends ElementHandler {
|
||||
@@ -30,8 +31,12 @@ public class MeasureHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
// EpilogueBackend has builtin support for logging measures
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import static javax.lang.model.type.TypeKind.SHORT;
|
||||
import java.util.Set;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public class PrimitiveHandler extends ElementHandler {
|
||||
@@ -35,7 +36,11 @@ public class PrimitiveHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class SendableHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
var dataType = dataType(element);
|
||||
|
||||
// Do not log commands or subsystems via their sendable implementations
|
||||
@@ -66,7 +66,7 @@ public class SendableHandler extends ElementHandler {
|
||||
return "logSendable(backend.getNested(\""
|
||||
+ loggedName(element)
|
||||
+ "\"), "
|
||||
+ elementAccess(element)
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
@@ -38,11 +39,11 @@ public class StructHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element)
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ", "
|
||||
+ structAccess(dataType(element))
|
||||
+ ")";
|
||||
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public class SupplierHandler extends ElementHandler {
|
||||
@@ -42,15 +43,19 @@ public class SupplierHandler extends ElementHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
public String logInvocation(Element element, TypeElement loggedClass) {
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element, loggedClass)
|
||||
+ ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String elementAccess(Element element) {
|
||||
public String elementAccess(Element element, TypeElement loggedClass) {
|
||||
var typeUtils = m_processingEnv.getTypeUtils();
|
||||
var dataType = dataType(element);
|
||||
String base = super.elementAccess(element);
|
||||
String base = super.elementAccess(element, loggedClass);
|
||||
|
||||
if (typeUtils.isAssignable(dataType, m_booleanSupplier)) {
|
||||
return base + ".getAsBoolean()";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user