From 20ecd5132c578c400fbf0546717b5906a9a43f93 Mon Sep 17 00:00:00 2001 From: PenguinEncounter <49845522+penguinencounter@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:45:55 -0700 Subject: [PATCH] Add generic type DeviceAnnotation - used for hardware annotations. - AnnotationT is the type of the annotation - TargetT is the target value constraint - typechecking and application is done internally - the lambdas are back! --- .../ftc/teamcode/hardware/HardwareMapper.java | 106 ++++++++++++------ 1 file changed, 72 insertions(+), 34 deletions(-) diff --git a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/hardware/HardwareMapper.java b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/hardware/HardwareMapper.java index e4a605b764b..ef03775690a 100644 --- a/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/hardware/HardwareMapper.java +++ b/TeamCode/src/main/java/org/firstinspires/ftc/teamcode/hardware/HardwareMapper.java @@ -4,12 +4,80 @@ import com.qualcomm.robotcore.hardware.DcMotorSimple; import com.qualcomm.robotcore.hardware.HardwareMap; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; /** * Annotation processor for hardware map things. */ public abstract class HardwareMapper { + /** + * Represents an annotation processor for hardware devices. + * Internals are a bit of a mess right now. + *

+ * HardwareName is hardcoded because it provides the value of the field. + */ + static final class DeviceAnnotation { + private final Class annotationClass; + private final Class targetClass; + private final Action action; + + @FunctionalInterface + interface Action { + void use(AnnotationT annotation, TargetType target); + } + + DeviceAnnotation(Class annotationClass, Class targetClass, Action action) { + this.annotationClass = annotationClass; + this.targetClass = targetClass; + this.action = action; + } + + private boolean check(Field t, Object value) { + if (!t.isAnnotationPresent(annotationClass)) return false; + AnnotationT annotation = t.getAnnotation(annotationClass); + if (annotation == null) + throw new NullPointerException("Annotation is null! (isAnnotationPresent is true but getAnnotation returned null)"); + if (!targetClass.isAssignableFrom(value.getClass())) { + throw new ClassCastException("Annotation " + annotationClass.getName() + " can only be used on " + targetClass.getName() + " (or subclasses)"); + } + return true; + } + + void use(Field t, Object value) { + if (!check(t, value)) return; + AnnotationT annotation = t.getAnnotation(annotationClass); + if (annotation == null) + throw new NullPointerException("Annotation disappeared between check and use"); + action.use(annotation, targetClass.cast(value)); + } + } + + static final DeviceAnnotation reversed = + new DeviceAnnotation<>( + Reversed.class, + DcMotorSimple.class, + (annotation, target) -> target.setDirection(DcMotorSimple.Direction.REVERSE) + ); + + static final DeviceAnnotation zeroPower = + new DeviceAnnotation<>( + ZeroPower.class, + DcMotor.class, + (annotation, target) -> target.setZeroPowerBehavior(annotation.value()) + ); + + static final DeviceAnnotation autoClearEncoder = + new DeviceAnnotation<>( + AutoClearEncoder.class, + DcMotor.class, + (annotation, target) -> { + DcMotor.RunMode current = target.getMode(); + target.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); + target.setMode(current); + } + ); + public HardwareMapper(HardwareMap map) { Field[] fields = this.getClass().getDeclaredFields(); for (Field field : fields) { @@ -22,12 +90,7 @@ public HardwareMapper(HardwareMap map) { Object result = map.tryGet(targetType, field.getName()); if (result == null) { - throw new RuntimeException( - "Hardware: '" + - field.getName() + "' not found, expected type " + - targetType.getName() + " for field " + - field.getName() + " in " + this.getClass().getName() - ); + throw new RuntimeException("Hardware: '" + field.getName() + "' not found, expected type " + targetType.getName() + " for field " + field.getName() + " in " + this.getClass().getName()); } try { field.set(this, result); @@ -35,34 +98,9 @@ public HardwareMapper(HardwareMap map) { throw new RuntimeException("Field " + field.getName() + " assign failed: " + result.getClass().getName() + " to " + field.getType().getName()); } - if (field.isAnnotationPresent(Reversed.class)) { - if (targetType.isAssignableFrom(DcMotorSimple.class)) { - DcMotorSimple motor = (DcMotorSimple) result; - motor.setDirection(DcMotorSimple.Direction.REVERSE); - } else { - throw new RuntimeException("@Reversed annotation can only be used on DcMotorSimple (or subclasses)"); - } - } - if (field.isAnnotationPresent(ZeroPower.class)) { - if (targetType.isAssignableFrom(DcMotor.class)) { - DcMotor motor = (DcMotor) result; - ZeroPower zeroPower = field.getAnnotation(ZeroPower.class); - if (zeroPower == null) throw new RuntimeException("ZeroPower annotation is null?!"); - motor.setZeroPowerBehavior(zeroPower.value()); - } else { - throw new RuntimeException("@ZeroPower annotation can only be used on DcMotor (or subclasses)"); - } - } - if (field.isAnnotationPresent(AutoClearEncoder.class)) { - if (targetType.isAssignableFrom(DcMotor.class)) { - DcMotor motor = (DcMotor) result; - DcMotor.RunMode current = motor.getMode(); - motor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); - motor.setMode(current); - } else { - throw new RuntimeException("@AutoClearEncoder annotation can only be used on DcMotor (or subclasses)"); - } - } + reversed.use(field, result); + zeroPower.use(field, result); + autoClearEncoder.use(field, result); } catch (IllegalArgumentException e) { throw new RuntimeException("Field " + field.getName() + " typecast failed"); } finally {