[epilogue] Allow custom loggers for generic types (#7452)

Support custom loggers for generic types
Improve error messaging for custom loggers with generic type arguments
Consistently start all epilogue processor prints with "[EPILOGUE]"
This commit is contained in:
Sam Carlberg
2024-11-30 01:11:46 -05:00
committed by GitHub
parent 806d56e564
commit 7d178615fa
3 changed files with 126 additions and 7 deletions

View File

@@ -86,7 +86,7 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Custom logger classes should have a @CustomLoggerFor annotation",
"[EPILOGUE] Custom logger classes should have a @CustomLoggerFor annotation",
e);
});
@@ -284,7 +284,7 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Logger classes must have a public no-argument constructor",
"[EPILOGUE] Logger classes must have a public no-argument constructor",
annotatedElement);
continue;
}
@@ -306,7 +306,17 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Multiple custom loggers detected for type " + targetType,
"[EPILOGUE] Multiple custom loggers detected for type " + targetType,
annotatedElement);
continue;
}
if (annotatedElement instanceof TypeElement t && !t.getTypeParameters().isEmpty()) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"[EPILOGUE] Custom logger classes cannot take generic type arguments",
annotatedElement);
continue;
}
@@ -318,7 +328,7 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Not a subclass of ClassSpecificLogger<" + targetType + ">",
"[EPILOGUE] Not a subclass of ClassSpecificLogger<" + targetType + ">",
annotatedElement);
continue;
}
@@ -368,7 +378,7 @@ public class AnnotationProcessor extends AbstractProcessor {
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Could not write logger file for " + clazz.getQualifiedName(),
"[EPILOGUE] Could not write logger file for " + clazz.getQualifiedName(),
clazz);
e.printStackTrace(System.err);
}

View File

@@ -22,13 +22,22 @@ public class ConfiguredLoggerHandler extends ElementHandler {
@Override
public boolean isLoggable(Element element) {
return m_customLoggers.containsKey(dataType(element));
return m_customLoggers.keySet().stream()
.anyMatch(m -> m_processingEnv.getTypeUtils().isAssignable(dataType(element), m));
}
@Override
public String logInvocation(Element element) {
var dataType = dataType(element);
var loggerType = m_customLoggers.get(dataType);
var loggerType =
m_customLoggers.entrySet().stream()
.filter(
e -> {
return m_processingEnv.getTypeUtils().isAssignable(dataType, e.getKey());
})
.findFirst()
.orElseThrow()
.getValue();
return "Epilogue."
+ StringUtils.lowerCamelCase(loggerType.asElement().getSimpleName())

View File

@@ -1470,6 +1470,106 @@ class AnnotationProcessorTest {
assertLoggerGenerates(source, expectedGeneratedSource);
}
@Test
void customGenericLogger() {
String source =
"""
package edu.wpi.first.epilogue;
import edu.wpi.first.epilogue.logging.*;
import edu.wpi.first.math.numbers.*;
import edu.wpi.first.math.Num;
import edu.wpi.first.math.Vector;
@CustomLoggerFor(Vector.class)
class VectorLogger extends ClassSpecificLogger<Vector<?>> {
public VectorLogger() {
super((Class) Vector.class);
}
@Override
public void update(DataLogger dataLogger, Vector<?> object) {
// Implementation is irrelevant
}
}
@Logged
class Example {
Vector<N3> vec;
}
""";
String expectedGeneratedSource =
"""
package edu.wpi.first.epilogue;
import edu.wpi.first.epilogue.Logged;
import edu.wpi.first.epilogue.Epilogue;
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
import edu.wpi.first.epilogue.logging.DataLogger;
public class ExampleLogger extends ClassSpecificLogger<Example> {
public ExampleLogger() {
super(Example.class);
}
@Override
public void update(DataLogger dataLogger, Example object) {
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
Epilogue.vectorLogger.tryUpdate(dataLogger.getSubLogger("vec"), object.vec, Epilogue.getConfig().errorHandler);
}
}
}
""";
assertLoggerGenerates(source, expectedGeneratedSource);
}
@Test
void genericLoggerForGenericType() {
String source =
"""
package edu.wpi.first.epilogue;
import edu.wpi.first.epilogue.logging.*;
class Generic<T> { }
@CustomLoggerFor(Generic.class)
// Invalid: loggers cannot take type arguments
class GenericLogger<T> extends ClassSpecificLogger<Generic<T>> {
public GenericLogger() {
super((Class) Generic.class);
}
@Override
public void update(DataLogger dataLogger, Generic<T> object) {
// Implementation is irrelevant
}
}
@Logged
class Example {
Generic<String> genericField;
}
""";
Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.withProcessors(new AnnotationProcessor())
.compile(JavaFileObjects.forSourceString("edu.wpi.first.epilogue.Example", source));
assertThat(compilation).failed();
assertThat(compilation).hadErrorCount(1);
assertCompilationError(
"[EPILOGUE] Custom logger classes cannot take generic type arguments",
9,
1,
compilation.errors().get(0));
}
@Test
void warnsAboutNonLoggableFields() {
String source =