From 1bf854f7e1acb531fc9f3b45c0216c0b44ba9f72 Mon Sep 17 00:00:00 2001 From: wilsonwatson Date: Wed, 10 Dec 2025 17:28:25 -0600 Subject: [PATCH 1/7] generate empty io --- .vscode/settings.json | 3 + build.gradle | 15 +- gen_io_types/.gitignore | 2 - gen_io_types/Cargo.toml | 14 - gen_io_types/src/java.rs | 192 ---------- gen_io_types/src/main.rs | 103 ------ .../java/org/frc5572/robotools/Checks.java | 20 -- .../frc5572/robotools/ClassListVisitor.java | 32 ++ .../frc5572/robotools/CompilationData.java | 137 ------- .../java/org/frc5572/robotools/IOTypes.java | 223 ------------ .../org/frc5572/robotools/RobotPlugin.java | 44 --- .../org/frc5572/robotools/RobotProcessor.java | 129 +++++-- .../org/frc5572/robotools/checks/Check.java | 12 - .../org/frc5572/robotools/checks/IOCheck.java | 136 ------- .../services/com.sun.source.util.Plugin | 1 - vendordeps/NavX.json | 40 --- vendordeps/Phoenix6.json | 339 ------------------ vendordeps/REVLib.json | 74 ---- 18 files changed, 126 insertions(+), 1390 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 gen_io_types/.gitignore delete mode 100644 gen_io_types/Cargo.toml delete mode 100644 gen_io_types/src/java.rs delete mode 100644 gen_io_types/src/main.rs delete mode 100644 src/main/java/org/frc5572/robotools/Checks.java create mode 100644 src/main/java/org/frc5572/robotools/ClassListVisitor.java delete mode 100755 src/main/java/org/frc5572/robotools/CompilationData.java delete mode 100644 src/main/java/org/frc5572/robotools/IOTypes.java delete mode 100644 src/main/java/org/frc5572/robotools/RobotPlugin.java delete mode 100755 src/main/java/org/frc5572/robotools/checks/Check.java delete mode 100755 src/main/java/org/frc5572/robotools/checks/IOCheck.java delete mode 100644 src/main/resources/META-INF/services/com.sun.source.util.Plugin delete mode 100644 vendordeps/NavX.json delete mode 100644 vendordeps/Phoenix6.json delete mode 100644 vendordeps/REVLib.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index e1b779c..eb1b179 100755 --- a/build.gradle +++ b/build.gradle @@ -4,19 +4,6 @@ plugins { id 'maven-publish' } -tasks.withType(JavaCompile) { - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED' - options.compilerArgs << '--add-exports' << 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED' -} - // sourceCompatibility = JavaVersion.VERSION_17 // targetCompatibility = JavaVersion.VERSION_17 @@ -25,7 +12,7 @@ repositories { } dependencies { - + implementation "com.squareup:javapoet:1.13.0" } publishing { diff --git a/gen_io_types/.gitignore b/gen_io_types/.gitignore deleted file mode 100644 index 9c71cc9..0000000 --- a/gen_io_types/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Cargo.lock -/target \ No newline at end of file diff --git a/gen_io_types/Cargo.toml b/gen_io_types/Cargo.toml deleted file mode 100644 index 6a12474..0000000 --- a/gen_io_types/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "gen_io_types" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -vendordeps = { git = "https://github.com/wilsonwatson/vendordeps.git" } -binrw = "0.13" -zip = "0.6" -tokio = { version = "1", features = ["full"] } -tempfile = "3.9" -serde_json = "1" \ No newline at end of file diff --git a/gen_io_types/src/java.rs b/gen_io_types/src/java.rs deleted file mode 100644 index 4866283..0000000 --- a/gen_io_types/src/java.rs +++ /dev/null @@ -1,192 +0,0 @@ -use binrw::BinRead; - -#[doc = "Enough of a Java Class to figure out its name, package and what it extends/implements"] -#[binrw::binread] -#[br(big, magic = b"\xca\xfe\xba\xbe")] -pub struct MinimalJavaClass { - #[br(temp)] - _minor_version: u16, - #[br(temp)] - _major_version: u16, - constant_pool: ConstantPool, - access_flags: u16, - this_class: u16, - super_class: u16, - #[br(temp)] - interfaces_count: u16, - #[br(count = interfaces_count)] - _interfaces: Vec, -} - -#[allow(unused)] -impl MinimalJavaClass { - pub fn this_class(&self) -> String { - match &self.constant_pool.0[self.this_class as usize - 1] { - ConstantPoolItem::Class { name_index } => { - match &self.constant_pool.0[*name_index as usize - 1] { - ConstantPoolItem::Utf8 { value } => { - return value.clone(); - } - x => panic!("cpool[this_class.name_index] is not a ConstantPoolItem::Utf8... Instead got {:?}", x) - } - }, - x => panic!("this_class is not a ConstantPoolItem::Class... Instead got {:?}", x) - } - } - - pub fn super_class(&self) -> String { - match &self.constant_pool.0[self.super_class as usize - 1] { - ConstantPoolItem::Class { name_index } => { - match &self.constant_pool.0[*name_index as usize - 1] { - ConstantPoolItem::Utf8 { value } => { - return value.clone(); - } - x => panic!("cpool[super_class.name_index] is not a ConstantPoolItem::Utf8... Instead got {:?}", x) - } - }, - x => panic!("super_class is not a ConstantPoolItem::Class... Instead got {:?}", x) - } - } - - pub fn is_public(&self) -> bool { - (self.access_flags & 0x0001) != 0 - } - - pub fn is_interface(&self) -> bool { - (self.access_flags & 0x0200) != 0 - } - - pub fn is_synthetic(&self) -> bool { - (self.access_flags & 0x1000) != 0 - } - - pub fn is_annotation(&self) -> bool { - (self.access_flags & 0x2000) != 0 - } - - pub fn is_enum(&self) -> bool { - (self.access_flags & 0x4000) != 0 - } -} - -struct ConstantPool(Vec); - -impl BinRead for ConstantPool { - type Args<'a> = (); - - fn read_options( - reader: &mut R, - _endian: binrw::Endian, - _args: Self::Args<'_>, - ) -> binrw::prelude::BinResult { - let cpool_count = u16::read_be(reader)?; - let mut cpool = Vec::new(); - let mut i = 1; - loop { - if i >= cpool_count { - break - } - let pos = reader.stream_position()?; - let item = ConstantPoolItem::read_be(reader)?; - let bump = match &item { - ConstantPoolItem::Long { .. } | ConstantPoolItem::Double { .. } => 2, - ConstantPoolItem::Skip => return Err(binrw::Error::AssertFail { pos, message: format!("Invalid Constant Pool Item.") }), - _ => 1 - }; - cpool.push(item); - for _ in 1..bump { - cpool.push(ConstantPoolItem::Skip); - } - - i += bump; - } - Ok(Self(cpool)) - } -} - -#[doc = "Constant Pool Item enum. Defined in SE Spec sec 4.4"] -#[binrw::binread] -#[derive(Debug)] -enum ConstantPoolItem { - #[br(magic = 7u8)] - Class { - name_index: u16, - }, - #[br(magic = 9u8)] - Fieldref { - _class_index: u16, - _name_and_type_index: u16, - }, - #[br(magic = 10u8)] - Methodref { - _class_index: u16, - _name_and_type_index: u16, - }, - #[br(magic = 11u8)] - InterfaceMethodref { - _class_index: u16, - _name_and_type_index: u16, - }, - #[br(magic = 8u8)] - String { - _string_index: u16, - }, - #[br(magic = 3u8)] - Integer { - _bytes: u32, - }, - #[br(magic = 4u8)] - Float { - _bytes: u32, - }, - #[br(magic = 5u8)] - Long { - _bytes: u64, - }, - #[br(magic = 6u8)] - Double { - _bytes: u64, - }, - #[br(magic = 12u8)] - NameAndType { - _name_index: u16, - _descriptor_index: u16, - }, - #[br(magic = 1u8)] - Utf8 { - #[br(temp)] - length: u16, - // Technically, Java supports CESU-8, not UTF-8, but this is close enough for now. - #[br(count = length, try_map = |x: Vec| String::from_utf8(x))] - value: String, - }, - #[br(magic = 15u8)] - MethodHandle { - _reference_kind: u8, - _reference_index: u16, - }, - #[br(magic = 16u8)] - MethodType { - _descriptor_index: u16, - }, - #[br(magic = 17u8)] - Dynamic { - _bootstrap_method_attr_index: u16, - _name_and_type_index: u16, - }, - #[br(magic = 18u8)] - InvokeDynamic { - _bootstrap_method_attr_index: u16, - _name_and_type_index: u16, - }, - #[br(magic = 19u8)] - Module { - _name_index: u16, - }, - #[br(magic = 20u8)] - Package { - _name_index: u16, - }, - - Skip, -} diff --git a/gen_io_types/src/main.rs b/gen_io_types/src/main.rs deleted file mode 100644 index 8ba970d..0000000 --- a/gen_io_types/src/main.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::{collections::HashMap, io::{Read, Write}}; - -use binrw::BinRead; -use tempfile::tempdir; - - -mod java; - -#[tokio::main] -async fn main() { - let source_dir = std::path::Path::new(file!()).canonicalize().unwrap(); - let source_dir = source_dir.parent().unwrap().parent().unwrap().parent().unwrap(); - - let temp_dir = tempdir().unwrap(); - - let mut interfaces = String::from("{"); - let mut classes = String::from("{"); - - let wpi = std::fs::read(source_dir.join("config.json")).unwrap(); - let wpi: HashMap> = serde_json::from_slice(&wpi).unwrap(); - for interface in &wpi["interfaces"] { - interfaces.push_str(&format!("\n {:?},", interface)); - } - for class in &wpi["classes"] { - classes.push_str(&format!("\n {:?},", class)); - } - - let skip = wpi["skip"].clone(); - - let vendor_deps_dir = source_dir.join("vendordeps"); - for item in std::fs::read_dir(vendor_deps_dir).unwrap() { - let item = item.unwrap(); - match item.path().extension().and_then(|x| x.to_str()) { - Some("json") => { - let vendordep: vendordeps::VendorDep = match serde_json::from_reader(std::fs::File::open(item.path()).unwrap()) { - Ok(x) => x, - Err(x) => { - eprintln!("Invalid format for vendordep at {}", item.path().display()); - eprintln!("{}", x); - continue - }, - }; - vendordep.download_all_java_deps_to_folder(temp_dir.path()).await.unwrap(); - } - _ => {} - } - } - - for item in std::fs::read_dir(temp_dir.path()).unwrap() { - let item = item.unwrap(); - if item.file_type().unwrap().is_dir() { - continue - } - let f = std::fs::File::open(item.path()).unwrap(); - let mut z = zip::ZipArchive::new(f).unwrap(); - for i in 0..z.len() { - let mut f = z.by_index(i).unwrap(); - if !f.name().ends_with(".class") { - continue - } - // ZipFile can't seek, so we need to fill it into a buffer first. - let mut res = Vec::new(); - f.read_to_end(&mut res).unwrap(); - - let mut res = std::io::Cursor::new(res); - let class = java::MinimalJavaClass::read_be(&mut res).unwrap(); - - if !class.is_public() { - continue - } - if class.is_annotation() { - continue - } - if class.is_enum() { - continue - } - - let name = class.this_class().replace('/', ".").replace('$', "."); - - if skip.contains(&name) { - continue - } - - if class.is_interface() { - interfaces.push_str(&format!("\n {:?},", name)); - } else { - classes.push_str(&format!("\n {:?},", name)); - } - } - } - - let mut outf = std::fs::File::create(source_dir.join("src/main/java/org/frc5572/robotools/IOTypes.java")).unwrap(); - writeln!(&mut outf, "package org.frc5572.robotools;\n").unwrap(); - writeln!(&mut outf, "/** Auto-generated list of IO interfaces and classes. */").unwrap(); - writeln!(&mut outf, "public class IOTypes {{\n").unwrap(); - writeln!(&mut outf, " /** List of interfaces disallowed a direct path to Robot.java. */").unwrap(); - writeln!(&mut outf, " public static String[] ioInterfaces = {}", interfaces).unwrap(); - writeln!(&mut outf, " }};").unwrap(); - writeln!(&mut outf, " /** List of classes disallowed a direct path to Robot.java. */").unwrap(); - writeln!(&mut outf, " public static String[] ioClasses = {}", classes).unwrap(); - writeln!(&mut outf, " }};").unwrap(); - writeln!(&mut outf, "}}\n").unwrap(); -} diff --git a/src/main/java/org/frc5572/robotools/Checks.java b/src/main/java/org/frc5572/robotools/Checks.java deleted file mode 100644 index 1971cc8..0000000 --- a/src/main/java/org/frc5572/robotools/Checks.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.frc5572.robotools; - -import org.frc5572.robotools.checks.Check; -import org.frc5572.robotools.checks.IOCheck; - -/** All checks to run. */ -public class Checks { - - private static final Check[] CHECKS = new Check[] {new IOCheck()}; - - /** Run through all checks */ - public static boolean process(CompilationData data) { - boolean fatal = false; - for (Check c : CHECKS) { - fatal = fatal || c.check(data); - } - return fatal; - } - -} diff --git a/src/main/java/org/frc5572/robotools/ClassListVisitor.java b/src/main/java/org/frc5572/robotools/ClassListVisitor.java new file mode 100644 index 0000000..b93d38a --- /dev/null +++ b/src/main/java/org/frc5572/robotools/ClassListVisitor.java @@ -0,0 +1,32 @@ +package org.frc5572.robotools; + +import java.util.List; + +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; + +public class ClassListVisitor extends SimpleAnnotationValueVisitor8 { + + private final List mirrors; + + public ClassListVisitor(List mirrors) { + super(); + this.mirrors = mirrors; + } + + @Override + public Void visitType(TypeMirror arg0, Void arg1) { + mirrors.add(arg0); + return null; + } + + @Override + public Void visitArray(List arg0, Void arg1) { + for(var item : arg0) { + this.visit(item); + } + return null; + } + +} diff --git a/src/main/java/org/frc5572/robotools/CompilationData.java b/src/main/java/org/frc5572/robotools/CompilationData.java deleted file mode 100755 index c7866ce..0000000 --- a/src/main/java/org/frc5572/robotools/CompilationData.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.frc5572.robotools; - -import javax.annotation.processing.Messager; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; -import com.sun.source.tree.CompilationUnitTree; -import com.sun.source.tree.LineMap; -import com.sun.source.tree.Tree; -import com.sun.source.util.JavacTask; -import com.sun.source.util.SourcePositions; -import com.sun.source.util.TaskEvent; -import com.sun.source.util.Trees; - -/** - * Wrapper around a TypeElement. Includes helpers for printing errors, warnings etc. - */ -public class CompilationData { - - /** Type Information for entire classpath. */ - public Types types; - private Trees trees; - private SourcePositions positions; - private CompilationUnitTree compilationUnitTree; - /** Element being processed */ - public TypeElement element; - private Messager messager; - - /** Basic constructor. */ - public CompilationData(Types types, Trees trees, SourcePositions positions, - CompilationUnitTree compilationUnitTree, TypeElement element, Messager messager) { - this.types = types; - this.trees = trees; - this.positions = positions; - this.compilationUnitTree = compilationUnitTree; - this.element = element; - this.messager = messager; - } - - /** Constructor from Javac Plugin context. */ - public CompilationData(JavacTask task, TaskEvent event) { - this(task.getTypes(), Trees.instance(task), Trees.instance(task).getSourcePositions(), - event.getCompilationUnit(), event.getTypeElement(), null); - } - - /** Constructor from Annotation Processor context. */ - public CompilationData(ProcessingEnvironment processingEnv, TypeElement element) { - this(processingEnv.getTypeUtils(), null, null, null, element, processingEnv.getMessager()); - } - - /** Show error */ - public void error(Element element, Object object) { - echo(element, object.toString(), Diagnostic.Kind.ERROR, "error", "Error"); - } - - /** Show warning */ - public void warn(Element element, Object object) { - echo(element, object.toString(), Diagnostic.Kind.MANDATORY_WARNING, "warning", "Warning"); - } - - /** Show note (this doesn't work in VS Code yet) */ - public void note(Element element, Object object) { - echo(element, object.toString(), Diagnostic.Kind.NOTE, "notice", "Note"); - } - - private void echo(Element element, String errString, Diagnostic.Kind kind, String ghString, - String humanString) { - if (compilationUnitTree == null) { - messager.printMessage(kind, errString, element); - } else { - Tree tree = trees.getTree(element); - LineMap linemap = compilationUnitTree.getLineMap(); - long pos = positions.getStartPosition(compilationUnitTree, tree); - long row = linemap.getLineNumber(pos); - String name = compilationUnitTree.getSourceFile().toUri().toString().split("/src/")[1]; - System.out - .println("::" + ghString + " file=src/" + name + ",line=" + row + "::" + errString); - } - } - - private boolean _implements(String qualifiedName, TypeElement elem) { - if (elem.getQualifiedName().toString().equals(qualifiedName)) { - return true; - } - Element superClass = types.asElement(elem.getSuperclass()); - if (superClass instanceof TypeElement) { - if (_implements(qualifiedName, (TypeElement) superClass)) { - return true; - } - } - for (var iface : elem.getInterfaces()) { - Element interface_ = types.asElement(iface); - if (interface_ instanceof TypeElement) { - if (_implements(qualifiedName, (TypeElement) interface_)) { - return true; - } - } - } - return false; - } - - private boolean _extends(String qualifiedName, TypeElement elem) { - if (elem.getQualifiedName().toString().equals(qualifiedName)) { - return true; - } - Element superClass = types.asElement(elem.getSuperclass()); - if (superClass instanceof TypeElement) { - if (_extends(qualifiedName, (TypeElement) superClass)) { - return true; - } - } - return false; - } - - /** Get if this type implements an interface by name. */ - public boolean implementsInterface(String qualifiedName) { - return _implements(qualifiedName, this.element); - } - - /** Get if the specified type implements an interface by name. */ - public boolean implementsInterface(TypeElement e, String qualifiedName) { - return _implements(qualifiedName, e); - } - - /** Get if this type extends a class by name. */ - public boolean extendsClass(String qualifiedName) { - return _extends(qualifiedName, this.element); - } - - /** Get if the specified type extends a class by name. */ - public boolean extendsClass(TypeElement e, String qualifiedName) { - return _extends(qualifiedName, e); - } - -} diff --git a/src/main/java/org/frc5572/robotools/IOTypes.java b/src/main/java/org/frc5572/robotools/IOTypes.java deleted file mode 100644 index cc11c01..0000000 --- a/src/main/java/org/frc5572/robotools/IOTypes.java +++ /dev/null @@ -1,223 +0,0 @@ -package org.frc5572.robotools; - -/** Auto-generated list of IO interfaces and classes. */ -public class IOTypes { - - /** List of interfaces disallowed a direct path to Robot.java. */ - public static String[] ioInterfaces = { - "edu.wpi.first.wpilibj.interfaces.Gyro", - "edu.wpi.first.wpilibj.interfaces.Accelerometer", - "edu.wpi.first.wpilibj.motorcontrol.MotorController", - "com.revrobotics.SparkMaxPIDController", - "com.revrobotics.CANSensor", - "com.revrobotics.CANEncoder", - "com.revrobotics.CANAnalog", - "com.revrobotics.RelativeEncoder", - "com.revrobotics.SparkMaxAbsoluteEncoder", - "com.revrobotics.AbsoluteEncoder", - "com.revrobotics.MotorFeedbackSensor", - "com.revrobotics.CANPIDController", - "com.revrobotics.SparkMaxLimitSwitch", - "com.revrobotics.SparkMaxRelativeEncoder", - "com.revrobotics.CANDigitalInput", - "com.revrobotics.AnalogInput", - "com.revrobotics.SparkMaxAnalogSensor", - "com.ctre.phoenix6.ISerializable", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest", - "com.ctre.phoenix6.hardware.ParentDevice.MapGenerator", - "com.ctre.phoenix6.configs.ParentConfiguration", - "com.kauailabs.navx.frc.ITimestampedDataSubscriber", - }; - /** List of classes disallowed a direct path to Robot.java. */ - public static String[] ioClasses = { - "edu.wpi.first.wpilibj.AnalogEncoder", - "edu.wpi.first.wpilibj.AnalogGyro", - "edu.wpi.first.wpilibj.AnalogInput", - "edu.wpi.first.wpilibj.AnalogOutput", - "edu.wpi.first.wpilibj.AnalogPotentiometer", - "edu.wpi.first.wpilibj.AnalogTrigger", - "edu.wpi.first.wpilibj.AnalogTriggerOutput", - "edu.wpi.first.wpilibj.DMA", - "edu.wpi.first.wpilibj.DigitalSource", - "edu.wpi.first.wpilibj.DoubleSolenoid", - "edu.wpi.first.wpilibj.Encoder", - "edu.wpi.first.wpilibj.I2C", - "edu.wpi.first.wpilibj.PWM", - "edu.wpi.first.wpilibj.PneumaticsBase", - "edu.wpi.first.wpilibj.PowerDistribution", - "edu.wpi.first.wpilibj.Relay", - "edu.wpi.first.wpilibj.SPI", - "edu.wpi.first.wpilibj.SerialPort", - "edu.wpi.first.wpilibj.Solenoid", - "com.revrobotics.CANSparkBase.ExternalFollower", - "com.revrobotics.ColorSensorV3", - "com.revrobotics.jni.CANSparkMaxJNI", - "com.revrobotics.jni.RevJNIWrapper", - "com.revrobotics.jni.CANSWDLJNI", - "com.revrobotics.SparkAbsoluteEncoder", - "com.revrobotics.SparkFlexExternalEncoder", - "com.revrobotics.ColorSensorV3.RawColor", - "com.revrobotics.CANSparkMaxLowLevel.PeriodicStatus2", - "com.revrobotics.SparkAnalogSensor", - "com.revrobotics.CANSparkMaxLowLevel", - "com.revrobotics.CIEColor", - "com.revrobotics.SparkRelativeEncoder", - "com.revrobotics.CANSparkLowLevel", - "com.revrobotics.CANSparkFlex", - "com.revrobotics.ColorMatchResult", - "com.revrobotics.CANSparkMax", - "com.revrobotics.SparkMaxAlternateEncoder", - "com.revrobotics.CANSparkLowLevel.PeriodicStatus1", - "com.revrobotics.CANSparkLowLevel.PeriodicStatus2", - "com.revrobotics.CANSparkLowLevel.FollowConfig", - "com.revrobotics.SparkLimitSwitch", - "com.revrobotics.CANSparkMaxLowLevel.PeriodicStatus0", - "com.revrobotics.ColorMatch", - "com.revrobotics.SparkPIDController", - "com.revrobotics.CANSparkLowLevel.FollowConfig.Config", - "com.revrobotics.CANSparkLowLevel.PeriodicStatus0", - "com.revrobotics.REVPhysicsSim", - "com.revrobotics.CANSparkBase", - "com.revrobotics.CANSparkMaxLowLevel.PeriodicStatus1", - "com.revrobotics.DataPortConfigUtil", - "com.ctre.phoenix6.Orchestra", - "com.ctre.phoenix6.sim.TalonFXSimState", - "com.ctre.phoenix6.sim.CANcoderSimState", - "com.ctre.phoenix6.sim.Pigeon2SimState", - "com.ctre.phoenix6.jni.ErrorReportingJNI", - "com.ctre.phoenix6.jni.StatusSignalJNI", - "com.ctre.phoenix6.jni.OrchestraJNI", - "com.ctre.phoenix6.jni.CANBusJNI", - "com.ctre.phoenix6.jni.SignalLoggerJNI", - "com.ctre.phoenix6.jni.PlatformJNI", - "com.ctre.phoenix6.jni.CtreJniWrapper", - "com.ctre.phoenix6.wpiutils.AutoFeedEnable", - "com.ctre.phoenix6.wpiutils.MotorSafetyImplem", - "com.ctre.phoenix6.wpiutils.CallbackHelper", - "com.ctre.phoenix6.SignalLogger", - "com.ctre.phoenix6.Utils", - "com.ctre.phoenix6.BaseStatusSignal", - "com.ctre.phoenix6.controls.jni.ControlJNI", - "com.ctre.phoenix6.controls.jni.ControlConfigJNI", - "com.ctre.phoenix6.controls.compound.Diff_VelocityDutyCycle_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_MotionMagicTorqueCurrentFOC_Position", - "com.ctre.phoenix6.controls.compound.Diff_VelocityDutyCycle_Position", - "com.ctre.phoenix6.controls.compound.Diff_VelocityTorqueCurrentFOC_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_VelocityVoltage_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_PositionTorqueCurrentFOC_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_MotionMagicDutyCycle_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_DutyCycleOut_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_TorqueCurrentFOC_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_VoltageOut_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_PositionVoltage_Position", - "com.ctre.phoenix6.controls.compound.Diff_PositionTorqueCurrentFOC_Position", - "com.ctre.phoenix6.controls.compound.Diff_MotionMagicVoltage_Position", - "com.ctre.phoenix6.controls.compound.Diff_PositionVoltage_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_VoltageOut_Position", - "com.ctre.phoenix6.controls.compound.Diff_PositionDutyCycle_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_MotionMagicTorqueCurrentFOC_Velocity", - "com.ctre.phoenix6.controls.compound.Diff_PositionDutyCycle_Position", - "com.ctre.phoenix6.controls.compound.Diff_VelocityTorqueCurrentFOC_Position", - "com.ctre.phoenix6.controls.compound.Diff_MotionMagicDutyCycle_Position", - "com.ctre.phoenix6.controls.compound.Diff_VelocityVoltage_Position", - "com.ctre.phoenix6.controls.compound.Diff_DutyCycleOut_Position", - "com.ctre.phoenix6.controls.compound.Diff_TorqueCurrentFOC_Position", - "com.ctre.phoenix6.controls.compound.Diff_MotionMagicVoltage_Velocity", - "com.ctre.phoenix6.CANBus", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.SwerveControlRequestParameters", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.RobotCentric", - "com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrain.OdometryThread", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.SwerveDriveBrake", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.ApplyChassisSpeeds", - "com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrainConstants", - "com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstants", - "com.ctre.phoenix6.mechanisms.swerve.utility.PhoenixPIDController", - "com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrain", - "com.ctre.phoenix6.mechanisms.swerve.SwerveModule", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.PointWheelsAt", - "com.ctre.phoenix6.mechanisms.swerve.SimSwerveDrivetrain.SimSwerveModule", - "com.ctre.phoenix6.mechanisms.swerve.SwerveModuleConstantsFactory", - "com.ctre.phoenix6.mechanisms.swerve.SimSwerveDrivetrain", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.FieldCentricFacingAngle", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.FieldCentric", - "com.ctre.phoenix6.mechanisms.swerve.SwerveDrivetrain.SwerveDriveState", - "com.ctre.phoenix6.mechanisms.swerve.SwerveRequest.Idle", - "com.ctre.phoenix6.mechanisms.DifferentialMechanism", - "com.ctre.phoenix6.mechanisms.SimpleDifferentialMechanism", - "com.ctre.phoenix6.StatusSignal.SignalMeasurement", - "com.ctre.phoenix6.unmanaged.jni.UnmanagedJNI", - "com.ctre.phoenix6.unmanaged.Unmanaged", - "com.ctre.phoenix6.AllTimestamps", - "com.ctre.phoenix6.StatusSignal", - "com.ctre.phoenix6.hardware.CANcoder", - "com.ctre.phoenix6.hardware.Pigeon2", - "com.ctre.phoenix6.hardware.jni.HardwareJNI", - "com.ctre.phoenix6.hardware.ParentDevice", - "com.ctre.phoenix6.hardware.DeviceIdentifier", - "com.ctre.phoenix6.hardware.core.CorePigeon2", - "com.ctre.phoenix6.hardware.core.CoreTalonFX", - "com.ctre.phoenix6.hardware.core.CoreCANcoder", - "com.ctre.phoenix6.hardware.TalonFX", - "com.ctre.phoenix6.CANBus.CANBusStatus", - "com.ctre.phoenix6.Timestamp", - "com.ctre.phoenix6.configs.CurrentLimitsConfigs", - "com.ctre.phoenix6.configs.ClosedLoopGeneralConfigs", - "com.ctre.phoenix6.configs.TalonFXConfiguration", - "com.ctre.phoenix6.configs.DifferentialConstantsConfigs", - "com.ctre.phoenix6.configs.AudioConfigs", - "com.ctre.phoenix6.configs.VoltageConfigs", - "com.ctre.phoenix6.configs.jni.ConfigJNI", - "com.ctre.phoenix6.configs.SoftwareLimitSwitchConfigs", - "com.ctre.phoenix6.configs.ParentConfigurator", - "com.ctre.phoenix6.configs.MagnetSensorConfigs", - "com.ctre.phoenix6.configs.CANcoderConfiguration", - "com.ctre.phoenix6.configs.MotionMagicConfigs", - "com.ctre.phoenix6.configs.SlotConfigs", - "com.ctre.phoenix6.configs.ClosedLoopRampsConfigs", - "com.ctre.phoenix6.configs.MountPoseConfigs", - "com.ctre.phoenix6.configs.Pigeon2Configuration", - "com.ctre.phoenix6.configs.MotorOutputConfigs", - "com.ctre.phoenix6.configs.OpenLoopRampsConfigs", - "com.ctre.phoenix6.configs.DifferentialSensorsConfigs", - "com.ctre.phoenix6.configs.GyroTrimConfigs", - "com.ctre.phoenix6.configs.Pigeon2FeaturesConfigs", - "com.ctre.phoenix6.configs.TorqueCurrentConfigs", - "com.ctre.phoenix6.configs.TalonFXConfigurator", - "com.ctre.phoenix6.configs.Pigeon2Configurator", - "com.ctre.phoenix6.configs.FeedbackConfigs", - "com.ctre.phoenix6.configs.CANcoderConfigurator", - "com.ctre.phoenix6.configs.Slot0Configs", - "com.ctre.phoenix6.configs.CustomParamsConfigs", - "com.ctre.phoenix6.configs.HardwareLimitSwitchConfigs", - "com.ctre.phoenix6.configs.Slot1Configs", - "com.ctre.phoenix6.configs.Slot2Configs", - "com.kauailabs.vmx.AHRSJNI", - "com.kauailabs.navx.AHRSProtocol.AHRSUpdate", - "com.kauailabs.navx.IMUProtocol.YPRUpdate", - "com.kauailabs.navx.AHRSProtocol.AHRSPosTSRawUpdate", - "com.kauailabs.navx.IMUProtocol.GyroUpdate", - "com.kauailabs.navx.frc.Tracer", - "com.kauailabs.navx.frc.AHRS.BoardYawAxis", - "com.kauailabs.navx.frc.IIOCompleteNotification.BoardState", - "com.kauailabs.navx.frc.AHRS", - "com.kauailabs.navx.frc.Quaternion", - "com.kauailabs.navx.IMUProtocol.QuaternionUpdate", - "com.kauailabs.navx.AHRSProtocol.AHRS_TUNING_VAR_ID", - "com.kauailabs.navx.IMUProtocol.StreamCommand", - "com.kauailabs.navx.AHRSProtocol.AHRSPosUpdate", - "com.kauailabs.navx.AHRSProtocol.AHRS_DATA_TYPE", - "com.kauailabs.navx.IMUProtocol", - "com.kauailabs.navx.AHRSProtocol.AHRSPosTSUpdate", - "com.kauailabs.navx.AHRSProtocol", - "com.kauailabs.navx.AHRSProtocol.AHRS_DATA_ACTION", - "com.kauailabs.navx.AHRSProtocol.MagCalData", - "com.kauailabs.navx.AHRSProtocol.AHRSUpdateBase", - "com.kauailabs.navx.AHRSProtocol.DataSetResponse", - "com.kauailabs.navx.AHRSProtocol.IntegrationControl", - "com.kauailabs.navx.IMURegisters", - "com.kauailabs.navx.IMUProtocol.StreamResponse", - "com.kauailabs.navx.AHRSProtocol.BoardID", - "com.kauailabs.navx.AHRSProtocol.TuningVar", - }; -} - diff --git a/src/main/java/org/frc5572/robotools/RobotPlugin.java b/src/main/java/org/frc5572/robotools/RobotPlugin.java deleted file mode 100644 index 6b8a624..0000000 --- a/src/main/java/org/frc5572/robotools/RobotPlugin.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.frc5572.robotools; - -import javax.lang.model.util.Types; -import com.sun.source.util.JavacTask; -import com.sun.source.util.Plugin; -import com.sun.source.util.SourcePositions; -import com.sun.source.util.TaskEvent; -import com.sun.source.util.TaskListener; -import com.sun.source.util.Trees; - -/** - * Javac plugin has source info, so it's used for Github Action annotations. - */ -public class RobotPlugin implements Plugin { - - /** - * Name used in build.gradle - */ - @Override - public String getName() { - return "rchk"; - } - - /** - * Function run when loaded - */ - @Override - public void init(JavacTask task, String... arg1) { - Types types = task.getTypes(); - Trees trees = Trees.instance(task); - SourcePositions positions = trees.getSourcePositions(); - task.addTaskListener(new TaskListener() { - @Override - public void finished(TaskEvent event) { - if (event.getKind() == TaskEvent.Kind.ANALYZE) { - CompilationData data = new CompilationData(types, trees, positions, - event.getCompilationUnit(), event.getTypeElement(), null); - Checks.process(data); - } - } - }); - } - -} diff --git a/src/main/java/org/frc5572/robotools/RobotProcessor.java b/src/main/java/org/frc5572/robotools/RobotProcessor.java index 7eee768..88265f2 100644 --- a/src/main/java/org/frc5572/robotools/RobotProcessor.java +++ b/src/main/java/org/frc5572/robotools/RobotProcessor.java @@ -1,75 +1,126 @@ package org.frc5572.robotools; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.ModuleElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; + +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; /** * Annotation processor for checks. Used by VS Code. */ -@SupportedAnnotationTypes("*") +@SupportedAnnotationTypes({ "frc.robot.util.GenerateEmptyIO" }) @SupportedSourceVersion(SourceVersion.RELEASE_11) -@SupportedOptions("frc_check.skip") public class RobotProcessor extends AbstractProcessor { - private boolean processTypeElement(TypeElement typeElement) { - for (Element e3 : typeElement.getEnclosedElements()) { - if (e3 instanceof TypeElement) { - processTypeElement((TypeElement) e3); - } - } - CompilationData data = new CompilationData(processingEnv, typeElement); - return Checks.process(data); - } - - private boolean hasProcessed; - /** Initialization function */ @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - hasProcessed = false; } /** Process all elements. */ @Override - public boolean process(Set arg0, RoundEnvironment roundEnv) { - if (hasProcessed) { - return false; - } - hasProcessed = true; - boolean fatal = false; - for (ModuleElement mod : processingEnv.getElementUtils().getAllModuleElements()) { - if (!mod.isUnnamed()) { - continue; + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (var anno : annotations) { + if (anno.getSimpleName().toString().equals("GenerateEmptyIO")) { + processGenerateEmptyIO(anno, roundEnv); + return true; } - for (Element element : mod.getEnclosedElements()) { - if (element instanceof PackageElement) { - PackageElement packageElement = (PackageElement) element; - if (packageElement.getQualifiedName().toString().startsWith("frc.")) { - for (Element element2 : packageElement.getEnclosedElements()) { - if (element2 instanceof TypeElement) { - TypeElement typeElement = (TypeElement) element2; - fatal = fatal || processTypeElement(typeElement); - } - } + } + return false; + } + + private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment roundEnv) { + roundEnv.getElementsAnnotatedWith(annotation).forEach(classElement_ -> { + TypeElement classElement = (TypeElement) classElement_; + String emptyClassName = classElement.getSimpleName() + "Empty"; + String emptyPackage = getPackageName(classElement); + + List params = new ArrayList<>(); + + for (var mirror : classElement.getAnnotationMirrors()) { + if (!mirror.getAnnotationType().asElement().getSimpleName().toString().equals("GenerateEmptyIO")) { + continue; + } + for (var ev : mirror.getElementValues().entrySet()) { + if (ev.getKey().getSimpleName().toString().equals("value")) { + ev.getValue().accept(new ClassListVisitor(params), null); } } } + + var specBuilder = TypeSpec.classBuilder(emptyClassName).addSuperinterface(TypeName.get(classElement.asType())).addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + AtomicInteger i = new AtomicInteger(); + var constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) + .addParameters(params.stream().map(ty -> { + return ParameterSpec.builder(TypeName.get(ty), "arg" + i.incrementAndGet()).build(); + }).toList()).build(); + + for (var element : classElement.getEnclosedElements()) { + if (element instanceof ExecutableElement javaMethod) { + specBuilder.addMethod(MethodSpec.methodBuilder(javaMethod.getSimpleName().toString()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(TypeName.VOID).addParameters(javaMethod.getParameters().stream().map(param -> { + return ParameterSpec + .builder(TypeName.get(param.asType()), param.getSimpleName().toString()) + .build(); + }).toList()).addCode("// Intentionally do nothing").build()); + } + } + + specBuilder = specBuilder.addMethod(constructor); + + var spec = specBuilder.build(); + + JavaFile file = JavaFile.builder(emptyPackage, spec).build(); + try { + file.writeTo(processingEnv.getFiler()); + } catch (IOException e) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.ERROR, "Failed to write class", classElement); + e.printStackTrace(); + } + }); + } + + private static String getPackageName(Element e) { + while (e != null) { + if (e.getKind().equals(ElementKind.PACKAGE)) { + return ((PackageElement) e).getQualifiedName().toString(); + } + e = e.getEnclosingElement(); } - if (fatal) { - throw new RuntimeException("Checks failed!"); - } - return false; + + return null; } } diff --git a/src/main/java/org/frc5572/robotools/checks/Check.java b/src/main/java/org/frc5572/robotools/checks/Check.java deleted file mode 100755 index 03a76dc..0000000 --- a/src/main/java/org/frc5572/robotools/checks/Check.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.frc5572.robotools.checks; - -import org.frc5572.robotools.CompilationData; - -/** A code checker */ -@FunctionalInterface -public interface Check { - - /** Check if code is fine. Returns true if error is fatal. */ - public boolean check(CompilationData data); - -} diff --git a/src/main/java/org/frc5572/robotools/checks/IOCheck.java b/src/main/java/org/frc5572/robotools/checks/IOCheck.java deleted file mode 100755 index 001b780..0000000 --- a/src/main/java/org/frc5572/robotools/checks/IOCheck.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.frc5572.robotools.checks; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor9; -import org.frc5572.robotools.CompilationData; -import org.frc5572.robotools.IOTypes; - -/** Performs checks to see that Subsystems do not contain raw IO types. */ -public class IOCheck implements Check { - - private void checkType(CompilationData data, Element[] warnPos, String path, TypeElement elem, - Set skip, String post) { - for (String iface : IOTypes.ioInterfaces) { - if (data.implementsInterface(elem, iface)) { - String[] parts = iface.split("\\."); - for (Element warnElem : warnPos) { - - data.error(warnElem, path + " is a " + parts[parts.length - 1] - + " which performs IO. It should be in an IO class!"); - - } - return; - } - } - for (String class_ : IOTypes.ioClasses) { - if (data.extendsClass(elem, class_)) { - String[] parts = class_.split("\\."); - for (Element warnElem : warnPos) { - - data.error(warnElem, path + " is a " + parts[parts.length - 1] - + " which performs IO. It should be in an IO class!"); - - } - return; - } - } - if (skip.contains(elem.getQualifiedName().toString())) { - return; - } - skip.add(elem.getQualifiedName().toString()); - outer: for (var item : elem.getEnclosedElements()) { - if (item instanceof VariableElement) { - VariableElement variable = (VariableElement) item; - for (AnnotationMirror mirror : item.getAnnotationMirrors()) { - Element annotationElement = mirror.getAnnotationType().asElement(); - if (annotationElement instanceof TypeElement) { - TypeElement annotationType = (TypeElement) annotationElement; - if (annotationType.getQualifiedName().toString() - .equals("java.lang.SuppressWarnings")) { - for (var entry : mirror.getElementValues().entrySet()) { - if (entry.getValue() - .accept(new SimpleAnnotationValueVisitor9() { - - @Override - public Boolean visitArray( - List vals, Void p) { - for (AnnotationValue v : vals) { - if (v.accept(this, null)) { - return true; - } - } - return false; - } - - @Override - public Boolean visitString(String s, Void p) { - if (s.equals("IOCheck")) { - return true; - } - return false; - } - }, null)) { - continue outer; - } - } - } - } - } - String post2 = ""; - TypeMirror typeMirror = variable.asType(); - while (typeMirror instanceof ArrayType) { - typeMirror = ((ArrayType) typeMirror).getComponentType(); - post2 += "[*]"; - } - Element element = data.types.asElement(typeMirror); - Element[] newElements = new Element[warnPos.length + 1]; - for (int i = 0; i < warnPos.length; i++) { - newElements[i] = warnPos[i]; - } - newElements[warnPos.length] = variable; - if (element instanceof TypeElement) { - TypeElement type = (TypeElement) element; - checkType(data, newElements, - path + post + "." + variable.getSimpleName().toString(), type, skip, post2); - } - } - } - } - - /** Perform check. */ - @Override - public boolean check(CompilationData data) { - if (data.implementsInterface("edu.wpi.first.wpilibj.RobotBase")) { - for (var item : data.element.getEnclosedElements()) { - if (item instanceof VariableElement) { - VariableElement variable = (VariableElement) item; - String path = - data.element.getQualifiedName() + "." + variable.getSimpleName().toString(); - String post = ""; - TypeMirror typeMirror = variable.asType(); - while (typeMirror instanceof ArrayType) { - typeMirror = ((ArrayType) typeMirror).getComponentType(); - post += "[*]"; - } - Element element = data.types.asElement(typeMirror); - if (element instanceof TypeElement) { - TypeElement type = (TypeElement) element; - Set skip = new HashSet<>(); - checkType(data, new Element[] {variable}, path, type, skip, post); - } - } - } - } - return false; - } - -} diff --git a/src/main/resources/META-INF/services/com.sun.source.util.Plugin b/src/main/resources/META-INF/services/com.sun.source.util.Plugin deleted file mode 100644 index 2057c50..0000000 --- a/src/main/resources/META-INF/services/com.sun.source.util.Plugin +++ /dev/null @@ -1 +0,0 @@ -org.frc5572.robotools.RobotPlugin \ No newline at end of file diff --git a/vendordeps/NavX.json b/vendordeps/NavX.json deleted file mode 100644 index e978a5f..0000000 --- a/vendordeps/NavX.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "fileName": "NavX.json", - "name": "NavX", - "version": "2024.1.0", - "uuid": "cb311d09-36e9-4143-a032-55bb2b94443b", - "frcYear": "2024", - "mavenUrls": [ - "https://dev.studica.com/maven/release/2024/" - ], - "jsonUrl": "https://dev.studica.com/releases/2024/NavX.json", - "javaDependencies": [ - { - "groupId": "com.kauailabs.navx.frc", - "artifactId": "navx-frc-java", - "version": "2024.1.0" - } - ], - "jniDependencies": [], - "cppDependencies": [ - { - "groupId": "com.kauailabs.navx.frc", - "artifactId": "navx-frc-cpp", - "version": "2024.1.0", - "headerClassifier": "headers", - "sourcesClassifier": "sources", - "sharedLibrary": false, - "libName": "navx_frc", - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "linuxathena", - "linuxraspbian", - "linuxarm32", - "linuxarm64", - "linuxx86-64", - "osxuniversal", - "windowsx86-64" - ] - } - ] -} \ No newline at end of file diff --git a/vendordeps/Phoenix6.json b/vendordeps/Phoenix6.json deleted file mode 100644 index 69a4079..0000000 --- a/vendordeps/Phoenix6.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "fileName": "Phoenix6.json", - "name": "CTRE-Phoenix (v6)", - "version": "24.1.0", - "frcYear": 2024, - "uuid": "e995de00-2c64-4df5-8831-c1441420ff19", - "mavenUrls": [ - "https://maven.ctr-electronics.com/release/" - ], - "jsonUrl": "https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2024-latest.json", - "conflictsWith": [ - { - "uuid": "3fcf3402-e646-4fa6-971e-18afe8173b1a", - "errorMessage": "The combined Phoenix-6-And-5 vendordep is no longer supported. Please remove the vendordep and instead add both the latest Phoenix 6 vendordep and Phoenix 5 vendordep.", - "offlineFileName": "Phoenix6And5.json" - } - ], - "javaDependencies": [ - { - "groupId": "com.ctre.phoenix6", - "artifactId": "wpiapi-java", - "version": "24.1.0" - } - ], - "jniDependencies": [ - { - "groupId": "com.ctre.phoenix6", - "artifactId": "tools", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "tools-sim", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonSRX", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonFX", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simVictorSPX", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simPigeonIMU", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simCANCoder", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simProTalonFX", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simProCANcoder", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simProPigeon2", - "version": "24.1.0", - "isJar": false, - "skipInvalidPlatforms": true, - "validPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - } - ], - "cppDependencies": [ - { - "groupId": "com.ctre.phoenix6", - "artifactId": "wpiapi-cpp", - "version": "24.1.0", - "libName": "CTRE_Phoenix6_WPI", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenix6", - "artifactId": "tools", - "version": "24.1.0", - "libName": "CTRE_PhoenixTools", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "linuxathena" - ], - "simMode": "hwsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "wpiapi-cpp-sim", - "version": "24.1.0", - "libName": "CTRE_Phoenix6_WPISim", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "tools-sim", - "version": "24.1.0", - "libName": "CTRE_PhoenixTools_Sim", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonSRX", - "version": "24.1.0", - "libName": "CTRE_SimTalonSRX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simTalonFX", - "version": "24.1.0", - "libName": "CTRE_SimTalonFX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simVictorSPX", - "version": "24.1.0", - "libName": "CTRE_SimVictorSPX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simPigeonIMU", - "version": "24.1.0", - "libName": "CTRE_SimPigeonIMU", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simCANCoder", - "version": "24.1.0", - "libName": "CTRE_SimCANCoder", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simProTalonFX", - "version": "24.1.0", - "libName": "CTRE_SimProTalonFX", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simProCANcoder", - "version": "24.1.0", - "libName": "CTRE_SimProCANcoder", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - }, - { - "groupId": "com.ctre.phoenix6.sim", - "artifactId": "simProPigeon2", - "version": "24.1.0", - "libName": "CTRE_SimProPigeon2", - "headerClassifier": "headers", - "sharedLibrary": true, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "linuxx86-64", - "osxuniversal" - ], - "simMode": "swsim" - } - ] -} \ No newline at end of file diff --git a/vendordeps/REVLib.json b/vendordeps/REVLib.json deleted file mode 100644 index 0f3520e..0000000 --- a/vendordeps/REVLib.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "fileName": "REVLib.json", - "name": "REVLib", - "version": "2024.2.0", - "frcYear": "2024", - "uuid": "3f48eb8c-50fe-43a6-9cb7-44c86353c4cb", - "mavenUrls": [ - "https://maven.revrobotics.com/" - ], - "jsonUrl": "https://software-metadata.revrobotics.com/REVLib-2024.json", - "javaDependencies": [ - { - "groupId": "com.revrobotics.frc", - "artifactId": "REVLib-java", - "version": "2024.2.0" - } - ], - "jniDependencies": [ - { - "groupId": "com.revrobotics.frc", - "artifactId": "REVLib-driver", - "version": "2024.2.0", - "skipInvalidPlatforms": true, - "isJar": false, - "validPlatforms": [ - "windowsx86-64", - "windowsx86", - "linuxarm64", - "linuxx86-64", - "linuxathena", - "linuxarm32", - "osxuniversal" - ] - } - ], - "cppDependencies": [ - { - "groupId": "com.revrobotics.frc", - "artifactId": "REVLib-cpp", - "version": "2024.2.0", - "libName": "REVLib", - "headerClassifier": "headers", - "sharedLibrary": false, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "windowsx86", - "linuxarm64", - "linuxx86-64", - "linuxathena", - "linuxarm32", - "osxuniversal" - ] - }, - { - "groupId": "com.revrobotics.frc", - "artifactId": "REVLib-driver", - "version": "2024.2.0", - "libName": "REVLibDriver", - "headerClassifier": "headers", - "sharedLibrary": false, - "skipInvalidPlatforms": true, - "binaryPlatforms": [ - "windowsx86-64", - "windowsx86", - "linuxarm64", - "linuxx86-64", - "linuxathena", - "linuxarm32", - "osxuniversal" - ] - } - ] -} \ No newline at end of file From 88997b19dc2f09b3e41a7698a85ce3e0b4567eb1 Mon Sep 17 00:00:00 2001 From: wilsonwatson Date: Wed, 10 Dec 2025 17:43:34 -0600 Subject: [PATCH 2/7] remove pipeline interacting with code that no longer exists --- .../workflows/{gen_io_types.yml => main.yml} | 30 ------------------- 1 file changed, 30 deletions(-) rename .github/workflows/{gen_io_types.yml => main.yml} (59%) diff --git a/.github/workflows/gen_io_types.yml b/.github/workflows/main.yml similarity index 59% rename from .github/workflows/gen_io_types.yml rename to .github/workflows/main.yml index f4642f6..88f6c0e 100644 --- a/.github/workflows/gen_io_types.yml +++ b/.github/workflows/main.yml @@ -5,36 +5,6 @@ on: permissions: contents: write jobs: - gen_io_types: - runs-on: ubuntu-latest - env: - # This line prevents the action from running after an automated push. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - # Check out push - - uses: actions/checkout@v3 - # Install Rust - - uses: dtolnay/rust-toolchain@stable - # Generate IOTypes.java - - name: Run gen_io_types - working-directory: gen_io_types - run: cargo run - - name: Install openjdk17 - run: sudo apt-get install openjdk-17-jdk - # Grant execute permission for gradlew - - name: Grant execute permission for gradlew - run: chmod +x gradlew - # Runs a single command using the runners shell - - name: Compile and run tests on robot code - run: ./gradlew build - # Commit and push new type - - name: Commit IOTypes - run: | - git config --global user.name 'Action' - git config --global user.email 'wilsonwatson@users.noreply.github.com' - git add -f src/main/java/org/frc5572/robotools/IOTypes.java - git commit -m "Generate IOTypes.java" || true - git push linting: name: Linting runs-on: ubuntu-latest From 3e39fb76fd51de52b12ddefba77ade3aca15ecaf Mon Sep 17 00:00:00 2001 From: wilsonwatson Date: Wed, 10 Dec 2025 17:45:50 -0600 Subject: [PATCH 3/7] fix linting --- checks.xml | 2 +- src/main/java/org/frc5572/robotools/ClassListVisitor.java | 4 +++- src/main/java/org/frc5572/robotools/RobotProcessor.java | 8 ++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/checks.xml b/checks.xml index f4f41c5..dc15ac8 100644 --- a/checks.xml +++ b/checks.xml @@ -42,7 +42,7 @@ - + diff --git a/src/main/java/org/frc5572/robotools/ClassListVisitor.java b/src/main/java/org/frc5572/robotools/ClassListVisitor.java index b93d38a..a794e03 100644 --- a/src/main/java/org/frc5572/robotools/ClassListVisitor.java +++ b/src/main/java/org/frc5572/robotools/ClassListVisitor.java @@ -6,10 +6,12 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor8; +/** Find all uses of a type in annotation values */ public class ClassListVisitor extends SimpleAnnotationValueVisitor8 { private final List mirrors; + /** Find all uses of a type in annotation values */ public ClassListVisitor(List mirrors) { super(); this.mirrors = mirrors; @@ -23,7 +25,7 @@ public Void visitType(TypeMirror arg0, Void arg1) { @Override public Void visitArray(List arg0, Void arg1) { - for(var item : arg0) { + for (var item : arg0) { this.visit(item); } return null; diff --git a/src/main/java/org/frc5572/robotools/RobotProcessor.java b/src/main/java/org/frc5572/robotools/RobotProcessor.java index 88265f2..5a88caa 100644 --- a/src/main/java/org/frc5572/robotools/RobotProcessor.java +++ b/src/main/java/org/frc5572/robotools/RobotProcessor.java @@ -65,7 +65,8 @@ private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment rou List params = new ArrayList<>(); for (var mirror : classElement.getAnnotationMirrors()) { - if (!mirror.getAnnotationType().asElement().getSimpleName().toString().equals("GenerateEmptyIO")) { + if (!mirror.getAnnotationType().asElement().getSimpleName() + .toString().equals("GenerateEmptyIO")) { continue; } for (var ev : mirror.getElementValues().entrySet()) { @@ -75,7 +76,10 @@ private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment rou } } - var specBuilder = TypeSpec.classBuilder(emptyClassName).addSuperinterface(TypeName.get(classElement.asType())).addModifiers(Modifier.PUBLIC, Modifier.FINAL); + var specBuilder = TypeSpec.classBuilder(emptyClassName) + .addSuperinterface( + TypeName.get(classElement.asType())) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); AtomicInteger i = new AtomicInteger(); var constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) From 0eb7c293d7eea4664900da6bd843fb09996d8149 Mon Sep 17 00:00:00 2001 From: wilsonwatson Date: Tue, 30 Dec 2025 21:46:28 -0600 Subject: [PATCH 4/7] typestatebuilder --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../robotools/AnnotationMirrorVisitor.java | 27 ++ .../org/frc5572/robotools/ClassVisitor.java | 13 + .../org/frc5572/robotools/RobotProcessor.java | 116 +++++- .../org/frc5572/robotools/StringVisitor.java | 12 + .../frc5572/robotools/TypeStateBuilder.java | 345 ++++++++++++++++++ 7 files changed, 504 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java create mode 100644 src/main/java/org/frc5572/robotools/ClassVisitor.java create mode 100644 src/main/java/org/frc5572/robotools/StringVisitor.java create mode 100644 src/main/java/org/frc5572/robotools/TypeStateBuilder.java diff --git a/build.gradle b/build.gradle index eb1b179..d870be0 100755 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ publishing { // Use jitpack format for publishing to mavenLocal groupId = 'com.github.Frc5572' artifactId = 'RobotTools' - version = 'main-SNAPSHOT' + version = 'test-SNAPSHOT' from components.java } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c23a1b3..fe93de0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=permwrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=permwrapper/dists diff --git a/src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java b/src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java new file mode 100644 index 0000000..a8354d0 --- /dev/null +++ b/src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java @@ -0,0 +1,27 @@ +package org.frc5572.robotools; + +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; + +public class AnnotationMirrorVisitor extends SimpleAnnotationValueVisitor8 { + + @Override + public AnnotationMirror visitAnnotation(AnnotationMirror arg0, Void arg1) { + return arg0; + } + + @Override + public AnnotationMirror visitArray(List arg0, Void arg1) { + for(var item : arg0) { + var res = this.visit(item, arg1); + if(res != null) { + return res; + } + } + return null; + } + +} diff --git a/src/main/java/org/frc5572/robotools/ClassVisitor.java b/src/main/java/org/frc5572/robotools/ClassVisitor.java new file mode 100644 index 0000000..1552130 --- /dev/null +++ b/src/main/java/org/frc5572/robotools/ClassVisitor.java @@ -0,0 +1,13 @@ +package org.frc5572.robotools; + +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; + +public class ClassVisitor extends SimpleAnnotationValueVisitor8 { + + @Override + public TypeMirror visitType(TypeMirror arg0, Void arg1) { + return arg0; + } + +} diff --git a/src/main/java/org/frc5572/robotools/RobotProcessor.java b/src/main/java/org/frc5572/robotools/RobotProcessor.java index 5a88caa..8b41deb 100644 --- a/src/main/java/org/frc5572/robotools/RobotProcessor.java +++ b/src/main/java/org/frc5572/robotools/RobotProcessor.java @@ -12,18 +12,16 @@ import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.AnnotationValueVisitor; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.ModuleElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; -import javax.lang.model.util.SimpleAnnotationValueVisitor8; +import javax.tools.Diagnostic.Kind; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; @@ -34,7 +32,7 @@ /** * Annotation processor for checks. Used by VS Code. */ -@SupportedAnnotationTypes({ "frc.robot.util.GenerateEmptyIO" }) +@SupportedAnnotationTypes({ "frc.robot.util.typestate.TypeStateBuilder", "frc.robot.util.GenerateEmptyIO" }) @SupportedSourceVersion(SourceVersion.RELEASE_11) public class RobotProcessor extends AbstractProcessor { @@ -47,13 +45,109 @@ public synchronized void init(ProcessingEnvironment processingEnv) { /** Process all elements. */ @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + boolean success = false; for (var anno : annotations) { + System.out.println("Trying " + anno.getQualifiedName()); if (anno.getSimpleName().toString().equals("GenerateEmptyIO")) { processGenerateEmptyIO(anno, roundEnv); - return true; + success = true; + } else if (anno.getSimpleName().toString().equals("TypeStateBuilder")) { + processGenerateTypeStateBuilder(anno, roundEnv); + success = true; } } - return false; + return success; + } + + private void processGenerateTypeStateBuilder(TypeElement annotation, RoundEnvironment roundEnv) { + roundEnv.getElementsAnnotatedWith(annotation).forEach(constructorElement_ -> { + ExecutableElement constructorElement = (ExecutableElement) constructorElement_; + Element parent_ = constructorElement.getEnclosingElement(); + if (!(parent_ instanceof TypeElement)) { + processingEnv.getMessager().printMessage(Kind.ERROR, + "TypeStateBuilder constructor must be the direct child of a TypeElement (e.g. class). Instead found " + + parent_.getKind().toString() + ".", + constructorElement); + } + TypeElement parent = (TypeElement) parent_; + String builderName = parent.getSimpleName() + "Builder"; + String builderPackage = getPackageName(parent); + System.out.println("Processing " + builderPackage + "." + builderName); + for(var mirror : constructorElement.getAnnotationMirrors()) { + if (!mirror.getAnnotationType().asElement().getSimpleName() + .toString().equals("TypeStateBuilder")) { + continue; + } + for (var ev : mirror.getElementValues().entrySet()) { + if (ev.getKey().getSimpleName().toString().equals("value")) { + String res = ev.getValue().accept(new StringVisitor(), null); + if(res != null) { + builderName = res; + } + } + } + } + + List fields = new ArrayList<>(); + List params = constructorElement.getParameters(); + for(int i = 0; i < params.size(); i++) { + boolean found = false; + VariableElement param = params.get(i); + for(var mirror : param.getAnnotationMirrors()) { + if (mirror.getAnnotationType().asElement().getSimpleName() + .toString().equals("InitField")) { + if(found) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", param); + } + fields.add(new TypeStateBuilder.InitField(param.asType(), param.getSimpleName().toString())); + found = true; + } else if (mirror.getAnnotationType().asElement().getSimpleName() + .toString().equals("RequiredField")) { + if(found) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", param); + } + fields.add(TypeStateBuilder.RequiredField.fromAnnotation(param.asType(), param.getSimpleName().toString(), mirror)); + found = true; + } else if (mirror.getAnnotationType().asElement().getSimpleName() + .toString().equals("OptionalField")) { + if(found) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", param); + } + fields.add(TypeStateBuilder.OptionalField.fromAnnotation(param.asType(), param.getSimpleName().toString(), mirror)); + found = true; + } + } + if(!found) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor must have one of @InitField, @RequiredField or @OptionalField", param); + } + } + + for(var field : fields) { + System.out.println("field " + field.name); + if(field instanceof TypeStateBuilder.MethodField method_field) { + if(method_field.alt != null) { + System.out.println(" alt"); + } + } + } + + var specBuilder = TypeSpec.classBuilder(builderName).addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + TypeStateBuilder typeStateBuilder = new TypeStateBuilder(builderName, fields.toArray(TypeStateBuilder.Field[]::new), parent.asType()); + typeStateBuilder.apply(specBuilder); + + var spec = specBuilder.build(); + + JavaFile file = JavaFile.builder(builderPackage, spec).build(); + try { + file.writeTo(processingEnv.getFiler()); + } catch (IOException e) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.ERROR, "Failed to write class", constructorElement); + e.printStackTrace(); + } + }); } private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment roundEnv) { @@ -66,7 +160,7 @@ private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment rou for (var mirror : classElement.getAnnotationMirrors()) { if (!mirror.getAnnotationType().asElement().getSimpleName() - .toString().equals("GenerateEmptyIO")) { + .toString().equals("GenerateEmptyIO")) { continue; } for (var ev : mirror.getElementValues().entrySet()) { @@ -77,9 +171,9 @@ private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment rou } var specBuilder = TypeSpec.classBuilder(emptyClassName) - .addSuperinterface( - TypeName.get(classElement.asType())) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + .addSuperinterface( + TypeName.get(classElement.asType())) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); AtomicInteger i = new AtomicInteger(); var constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) diff --git a/src/main/java/org/frc5572/robotools/StringVisitor.java b/src/main/java/org/frc5572/robotools/StringVisitor.java new file mode 100644 index 0000000..ed72122 --- /dev/null +++ b/src/main/java/org/frc5572/robotools/StringVisitor.java @@ -0,0 +1,12 @@ +package org.frc5572.robotools; + +import javax.lang.model.util.SimpleAnnotationValueVisitor8; + +public class StringVisitor extends SimpleAnnotationValueVisitor8 { + + @Override + public String visitString(String arg0, Void arg1) { + return arg0; + } + +} diff --git a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java new file mode 100644 index 0000000..8dc75dd --- /dev/null +++ b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java @@ -0,0 +1,345 @@ +package org.frc5572.robotools; + +import java.util.Arrays; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +public class TypeStateBuilder { + + private final Fields[] permutations; + private final int num_permutable_fields; + + public TypeStateBuilder(String name, Field[] fields_, + TypeMirror result) { + num_permutable_fields = (int) Arrays.stream(fields_).filter((x) -> !(x instanceof InitField)).count(); + permutations = new Fields[1 << num_permutable_fields]; + boolean[] start = new boolean[num_permutable_fields]; + Fields fields = new Fields(name, fields_, start, result); + while (true) { + int index = encodePermutationAsInt(start); + permutations[index] = fields; + start = Arrays.copyOf(start, start.length); + if (!advance(start)) { + break; + } + fields = new Fields(name, fields_, start, result); + } + } + + public void apply(TypeSpec.Builder builder) { + Fields base = permutations[encodePermutationAsInt(new boolean[num_permutable_fields])]; + base.write_fields(builder); + base.write_constructor(builder, true); + base.write_methods(builder, permutations); + for (int i = 1; i < permutations.length; i++) { + TypeSpec.Builder subBuilder = TypeSpec.classBuilder(permutations[i].class_name()) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + permutations[i].write_fields(subBuilder); + permutations[i].write_constructor(subBuilder, false); + permutations[i].write_methods(subBuilder, permutations); + builder.addType(subBuilder.build()); + } + } + + private static boolean advance(boolean[] permutation) { + for (int i = 0; i < permutation.length; i++) { + if (permutation[i]) { + permutation[i] = false; + } else { + permutation[i] = true; + return true; + } + } + return false; + } + + public static class Field { + public final TypeMirror type; + public final String name; + + public Field(TypeMirror type, String name) { + this.type = type; + this.name = name; + } + } + + public static class InitField extends Field { + public InitField(TypeMirror type, String name) { + super(type, name); + } + } + + public static class MethodField extends Field { + public final AltMethod alt; + + public MethodField(TypeMirror type, String name, AltMethod alt) { + super(type, name); + this.alt = alt; + } + + public MethodField(TypeMirror type, String name) { + this(type, name, null); + } + } + + public static class RequiredField extends MethodField { + + public RequiredField(TypeMirror type, String name, AltMethod alt) { + super(type, name, alt); + } + + public RequiredField(TypeMirror type, String name) { + super(type, name); + } + + public static RequiredField fromAnnotation(TypeMirror type, String name, AnnotationMirror mirror) { + AltMethod alt = null; + for (var ev : mirror.getElementValues().entrySet()) { + if (ev.getKey().getSimpleName().toString().equals("alt")) { + AnnotationMirror altMirror = ev.getValue().accept(new AnnotationMirrorVisitor(), null); + alt = AltMethod.fromAnnotation(altMirror, name); + } + } + return new RequiredField(type, name, alt); + } + } + + public static class OptionalField extends MethodField { + public final String default_code; + + public OptionalField(TypeMirror type, String name, AltMethod alt, String default_code) { + super(type, name, alt); + this.default_code = default_code; + } + + public OptionalField(TypeMirror type, String name, String default_code) { + super(type, name); + this.default_code = default_code; + } + + public static OptionalField fromAnnotation(TypeMirror type, String name, AnnotationMirror mirror) { + String defaultCode = ""; + AltMethod alt = null; + for (var ev : mirror.getElementValues().entrySet()) { + if (ev.getKey().getSimpleName().toString().equals("value")) { + defaultCode = ev.getValue().accept(new StringVisitor(), null); + } else if (ev.getKey().getSimpleName().toString().equals("alt")) { + AnnotationMirror altMirror = ev.getValue().accept(new AnnotationMirrorVisitor(), null); + alt = AltMethod.fromAnnotation(altMirror, name); + } + } + return new OptionalField(type, name, alt, defaultCode); + } + } + + public static record AltMethod(TypeMirror type, String parameterName, String code) { + public static AltMethod fromAnnotation(AnnotationMirror mirror, String defaultName) { + if(mirror == null) { + System.out.println("annotation is null"); + return null; + } + + if (!mirror.getAnnotationType().asElement().getSimpleName() + .toString().equals("AltMethod")) { + System.out.println("annotation name doesn't match " + mirror.getAnnotationType().asElement().getSimpleName() + .toString()); + return null; + } + TypeMirror type = null; + String parameterName = defaultName; + String code = null; + + for (var ev : mirror.getElementValues().entrySet()) { + if (ev.getKey().getSimpleName().toString().equals("type")) { + type = ev.getValue().accept(new ClassVisitor(), null); + } else if (ev.getKey().getSimpleName().toString().equals("parameter_name")) { + parameterName = ev.getValue().accept(new StringVisitor(), null); + } else if (ev.getKey().getSimpleName().toString().equals("value")) { + code = ev.getValue().accept(new StringVisitor(), null); + } + } + + if(type == null) { + System.out.println("Missing type"); + return null; + } + if(code == null) { + System.out.println("Missing code"); + return null; + } + + return new AltMethod(type, parameterName, code); + } + } + + public static record Fields(String name, Field[] fields, boolean[] permutation, TypeMirror result) { + public String class_name() { + StringBuilder sb = new StringBuilder(name); + for (int i = 0; i < permutation.length; i++) { + sb.append(permutation[i] ? 1 : 0); + } + return sb.toString(); + } + + public TypeName type_name() { + return ClassName.get("", class_name()); + } + + public boolean could_finish() { + int j = 0; + for (int i = 0; i < fields.length; i++) { + if (fields[i] instanceof MethodField) { + if (fields[i] instanceof RequiredField) { + if (!permutation[j]) { + return false; + } + } + j++; + } + } + return true; + } + + public boolean is_full() { + for (int i = 0; i < permutation.length; i++) { + if (!permutation[i]) { + return false; + } + } + return true; + } + + public boolean[] next_permutation(int index) { + boolean[] next_ = new boolean[permutation.length]; + System.arraycopy(permutation, 0, next_, 0, permutation.length); + next_[index] = true; + return next_; + } + + public void write_constructor(TypeSpec.Builder builder, boolean is_public) { + var constructor = MethodSpec.constructorBuilder() + .addModifiers(is_public ? Modifier.PUBLIC : Modifier.PRIVATE); + int j = 0; + for (int i = 0; i < fields.length; i++) { + if (fields[i] instanceof MethodField) { + if (!permutation[j++]) { + continue; + } + } + constructor.addParameter(TypeName.get(this.fields[i].type), this.fields[i].name + "_"); + constructor.addCode("this." + this.fields[i].name + "_ = " + this.fields[i].name + "_;\n"); + } + builder.addMethod(constructor.build()); + } + + public void write_fields(TypeSpec.Builder builder) { + int j = 0; + for (int i = 0; i < fields.length; i++) { + if (fields[i] instanceof MethodField) { + if (!permutation[j++]) { + continue; + } + } + builder.addField(FieldSpec + .builder(TypeName.get(fields[i].type), fields[i].name + "_", Modifier.PRIVATE, Modifier.FINAL) + .build()); + } + } + + private void write_method(TypeSpec.Builder builder, int index, MethodField field, TypeName result) { + var method = MethodSpec.methodBuilder(field.name).addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(result) + .addParameter(TypeName.get(field.type), field.name + "_"); + method.addCode("return new $T(", result); + int j = 0; + boolean is_first = true; + for (int i = 0; i < fields.length; i++) { + if (i == index) { + if (!is_first) { + method.addCode(", "); + } + method.addCode(field.name + "_"); + is_first = false; + } + if (fields[i] instanceof MethodField) { + if (!permutation[j++]) { + continue; + } + } + if (!is_first) { + method.addCode(", "); + } + method.addCode("this." + fields[i].name + "_"); + is_first = false; + } + method.addCode(");"); + builder.addMethod(method.build()); + if (field.alt != null) { + method = MethodSpec.methodBuilder(field.name).addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(result).addParameter(TypeName.get(field.alt.type), + field.alt.parameterName); + method.addCode("return this." + field.name + "(" + field.alt.code + ");"); + builder.addMethod(method.build()); + } + } + + private void write_finish(TypeSpec.Builder builder) { + var method = MethodSpec.methodBuilder("finish").addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(TypeName.get(result)); + method.addCode("return new $T(", TypeName.get(result)); + int j = 0; + for (int i = 0; i < fields.length; i++) { + if (i != 0) { + method.addCode(", "); + } + if (fields[i] instanceof OptionalField optional_field) { + if (!permutation[j++]) { + method.addCode(optional_field.default_code); + continue; + } + } + if (fields[i] instanceof RequiredField) { + j++; + } + method.addCode("this." + fields[i].name + "_"); + } + method.addCode(");"); + builder.addMethod(method.build()); + } + + public void write_methods(TypeSpec.Builder builder, Fields[] permutations) { + int j = 0; + for (int i = 0; i < this.fields.length; i++) { + if (fields[i] instanceof MethodField method_field) { + if (!permutation[j]) { + boolean[] next = next_permutation(j); + int next_index = encodePermutationAsInt(next); + TypeName result = permutations[next_index].type_name(); + write_method(builder, i, method_field, result); + } + j++; + } + } + if (could_finish()) { + write_finish(builder); + } + } + } + + private static int encodePermutationAsInt(boolean[] permutation) { + int res = 0; + for (int i = 0; i < permutation.length; i++) { + if (permutation[i]) { + res += 1; + } + res <<= 1; + } + return res >> 1; + } + +} From 45505d2da6915022093d49261df5f871d83bc288 Mon Sep 17 00:00:00 2001 From: wilsonwatson Date: Tue, 30 Dec 2025 22:17:11 -0600 Subject: [PATCH 5/7] remove debug print statements --- src/main/java/org/frc5572/robotools/RobotProcessor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/frc5572/robotools/RobotProcessor.java b/src/main/java/org/frc5572/robotools/RobotProcessor.java index 8b41deb..bf5413d 100644 --- a/src/main/java/org/frc5572/robotools/RobotProcessor.java +++ b/src/main/java/org/frc5572/robotools/RobotProcessor.java @@ -47,7 +47,7 @@ public synchronized void init(ProcessingEnvironment processingEnv) { public boolean process(Set annotations, RoundEnvironment roundEnv) { boolean success = false; for (var anno : annotations) { - System.out.println("Trying " + anno.getQualifiedName()); + // System.out.println("Trying " + anno.getQualifiedName()); if (anno.getSimpleName().toString().equals("GenerateEmptyIO")) { processGenerateEmptyIO(anno, roundEnv); success = true; @@ -72,7 +72,7 @@ private void processGenerateTypeStateBuilder(TypeElement annotation, RoundEnviro TypeElement parent = (TypeElement) parent_; String builderName = parent.getSimpleName() + "Builder"; String builderPackage = getPackageName(parent); - System.out.println("Processing " + builderPackage + "." + builderName); + // System.out.println("Processing " + builderPackage + "." + builderName); for(var mirror : constructorElement.getAnnotationMirrors()) { if (!mirror.getAnnotationType().asElement().getSimpleName() .toString().equals("TypeStateBuilder")) { @@ -123,10 +123,10 @@ private void processGenerateTypeStateBuilder(TypeElement annotation, RoundEnviro } for(var field : fields) { - System.out.println("field " + field.name); + // System.out.println("field " + field.name); if(field instanceof TypeStateBuilder.MethodField method_field) { if(method_field.alt != null) { - System.out.println(" alt"); + // System.out.println(" alt"); } } } From 8c9422a52b7ae3a1561bac29000fddde9e71a9b0 Mon Sep 17 00:00:00 2001 From: Watson Date: Mon, 5 Jan 2026 09:28:30 -0600 Subject: [PATCH 6/7] make builders linear instead of quadratic --- src/main/java/org/frc5572/robotools/TypeStateBuilder.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java index 8dc75dd..4f50918 100644 --- a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java +++ b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java @@ -40,6 +40,9 @@ public void apply(TypeSpec.Builder builder) { base.write_constructor(builder, true); base.write_methods(builder, permutations); for (int i = 1; i < permutations.length; i++) { + if(permutations[i] == null) { + continue; + } TypeSpec.Builder subBuilder = TypeSpec.classBuilder(permutations[i].class_name()) .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); permutations[i].write_fields(subBuilder); @@ -51,9 +54,7 @@ public void apply(TypeSpec.Builder builder) { private static boolean advance(boolean[] permutation) { for (int i = 0; i < permutation.length; i++) { - if (permutation[i]) { - permutation[i] = false; - } else { + if (!permutation[i]) { permutation[i] = true; return true; } @@ -321,6 +322,7 @@ public void write_methods(TypeSpec.Builder builder, Fields[] permutations) { int next_index = encodePermutationAsInt(next); TypeName result = permutations[next_index].type_name(); write_method(builder, i, method_field, result); + break; } j++; } From cc475ac053e5578a25d434b1c43f177dd66cba4c Mon Sep 17 00:00:00 2001 From: Watson Date: Wed, 7 Jan 2026 08:27:41 -0600 Subject: [PATCH 7/7] linting --- .vscode/settings.json | 72 +++++++++- .../robotools/AnnotationMirrorVisitor.java | 8 +- .../frc5572/robotools/ClassListVisitor.java | 3 +- .../org/frc5572/robotools/ClassVisitor.java | 1 + .../org/frc5572/robotools/RobotProcessor.java | 125 ++++++++++-------- .../org/frc5572/robotools/StringVisitor.java | 1 + .../frc5572/robotools/TypeStateBuilder.java | 107 ++++++++++----- 7 files changed, 222 insertions(+), 95 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c5f3f6b..0996538 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,73 @@ { - "java.configuration.updateBuildConfiguration": "interactive" + "java.configuration.updateBuildConfiguration": "automatic", + "java.server.launchMode": "Standard", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "bin/": true, + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true, + "**/*~": true + }, + "java.test.config": [ + { + "name": "WPIlibUnitTests", + "workingDirectory": "${workspaceFolder}/build/jni/release", + "vmargs": [ + "-Djava.library.path=${workspaceFolder}/build/jni/release" + ], + "env": { + "LD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release", + "DYLD_LIBRARY_PATH": "${workspaceFolder}/build/jni/release" + } + }, + ], + "java.test.defaultConfig": "WPIlibUnitTests", + "java.format.settings.url": "formatter.xml", + "java.format.settings.profile": "GoogleStyle", + "[java]": { + "editor.defaultFormatter": "redhat.java", + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "terminal.integrated.defaultProfile.windows": "Git Bash", + "java.import.gradle.annotationProcessing.enabled": false, + "java.completion.favoriteStaticMembers": [ + "org.junit.Assert.*", + "org.junit.Assume.*", + "org.junit.jupiter.api.Assertions.*", + "org.junit.jupiter.api.Assumptions.*", + "org.junit.jupiter.api.DynamicContainer.*", + "org.junit.jupiter.api.DynamicTest.*", + "org.mockito.Mockito.*", + "org.mockito.ArgumentMatchers.*", + "org.mockito.Answers.*", + "edu.wpi.first.units.Units.*" + ], + "java.completion.filteredTypes": [ + "java.awt.*", + "com.sun.*", + "sun.*", + "jdk.*", + "org.graalvm.*", + "io.micrometer.shaded.*", + "java.beans.*", + "java.util.Base64.*", + "java.util.Timer", + "java.sql.*", + "javax.swing.*", + "javax.management.*", + "javax.smartcardio.*", + "edu.wpi.first.math.proto.*", + "edu.wpi.first.math.**.proto.*", + "edu.wpi.first.math.**.struct.*", + ] } \ No newline at end of file diff --git a/src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java b/src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java index a8354d0..1b8ff21 100644 --- a/src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java +++ b/src/main/java/org/frc5572/robotools/AnnotationMirrorVisitor.java @@ -1,11 +1,11 @@ package org.frc5572.robotools; import java.util.List; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.util.SimpleAnnotationValueVisitor8; +/** Find all uses of an annotation in annotation values */ public class AnnotationMirrorVisitor extends SimpleAnnotationValueVisitor8 { @Override @@ -15,13 +15,13 @@ public AnnotationMirror visitAnnotation(AnnotationMirror arg0, Void arg1) { @Override public AnnotationMirror visitArray(List arg0, Void arg1) { - for(var item : arg0) { + for (var item : arg0) { var res = this.visit(item, arg1); - if(res != null) { + if (res != null) { return res; } } return null; } - + } diff --git a/src/main/java/org/frc5572/robotools/ClassListVisitor.java b/src/main/java/org/frc5572/robotools/ClassListVisitor.java index a794e03..6b8e9c9 100644 --- a/src/main/java/org/frc5572/robotools/ClassListVisitor.java +++ b/src/main/java/org/frc5572/robotools/ClassListVisitor.java @@ -1,14 +1,13 @@ package org.frc5572.robotools; import java.util.List; - import javax.lang.model.element.AnnotationValue; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor8; /** Find all uses of a type in annotation values */ public class ClassListVisitor extends SimpleAnnotationValueVisitor8 { - + private final List mirrors; /** Find all uses of a type in annotation values */ diff --git a/src/main/java/org/frc5572/robotools/ClassVisitor.java b/src/main/java/org/frc5572/robotools/ClassVisitor.java index 1552130..306aa03 100644 --- a/src/main/java/org/frc5572/robotools/ClassVisitor.java +++ b/src/main/java/org/frc5572/robotools/ClassVisitor.java @@ -3,6 +3,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor8; +/** Find all uses of a type in annotation values */ public class ClassVisitor extends SimpleAnnotationValueVisitor8 { @Override diff --git a/src/main/java/org/frc5572/robotools/RobotProcessor.java b/src/main/java/org/frc5572/robotools/RobotProcessor.java index bf5413d..9516e75 100644 --- a/src/main/java/org/frc5572/robotools/RobotProcessor.java +++ b/src/main/java/org/frc5572/robotools/RobotProcessor.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; - import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; @@ -22,7 +21,6 @@ import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; - import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; @@ -32,7 +30,8 @@ /** * Annotation processor for checks. Used by VS Code. */ -@SupportedAnnotationTypes({ "frc.robot.util.typestate.TypeStateBuilder", "frc.robot.util.GenerateEmptyIO" }) +@SupportedAnnotationTypes({"frc.robot.util.typestate.TypeStateBuilder", + "frc.robot.util.GenerateEmptyIO"}) @SupportedSourceVersion(SourceVersion.RELEASE_11) public class RobotProcessor extends AbstractProcessor { @@ -59,29 +58,30 @@ public boolean process(Set annotations, RoundEnvironment return success; } - private void processGenerateTypeStateBuilder(TypeElement annotation, RoundEnvironment roundEnv) { + private void processGenerateTypeStateBuilder(TypeElement annotation, + RoundEnvironment roundEnv) { roundEnv.getElementsAnnotatedWith(annotation).forEach(constructorElement_ -> { ExecutableElement constructorElement = (ExecutableElement) constructorElement_; Element parent_ = constructorElement.getEnclosingElement(); if (!(parent_ instanceof TypeElement)) { processingEnv.getMessager().printMessage(Kind.ERROR, - "TypeStateBuilder constructor must be the direct child of a TypeElement (e.g. class). Instead found " - + parent_.getKind().toString() + ".", - constructorElement); + "TypeStateBuilder constructor must be the direct child of a TypeElement (e.g. class). Instead found " + + parent_.getKind().toString() + ".", + constructorElement); } TypeElement parent = (TypeElement) parent_; String builderName = parent.getSimpleName() + "Builder"; String builderPackage = getPackageName(parent); // System.out.println("Processing " + builderPackage + "." + builderName); - for(var mirror : constructorElement.getAnnotationMirrors()) { - if (!mirror.getAnnotationType().asElement().getSimpleName() - .toString().equals("TypeStateBuilder")) { + for (var mirror : constructorElement.getAnnotationMirrors()) { + if (!mirror.getAnnotationType().asElement().getSimpleName().toString() + .equals("TypeStateBuilder")) { continue; } for (var ev : mirror.getElementValues().entrySet()) { if (ev.getKey().getSimpleName().toString().equals("value")) { String res = ev.getValue().accept(new StringVisitor(), null); - if(res != null) { + if (res != null) { builderName = res; } } @@ -90,50 +90,63 @@ private void processGenerateTypeStateBuilder(TypeElement annotation, RoundEnviro List fields = new ArrayList<>(); List params = constructorElement.getParameters(); - for(int i = 0; i < params.size(); i++) { + for (int i = 0; i < params.size(); i++) { boolean found = false; VariableElement param = params.get(i); - for(var mirror : param.getAnnotationMirrors()) { - if (mirror.getAnnotationType().asElement().getSimpleName() - .toString().equals("InitField")) { - if(found) { - processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", param); + for (var mirror : param.getAnnotationMirrors()) { + if (mirror.getAnnotationType().asElement().getSimpleName().toString() + .equals("InitField")) { + if (found) { + processingEnv.getMessager().printMessage(Kind.ERROR, + "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", + param); } - fields.add(new TypeStateBuilder.InitField(param.asType(), param.getSimpleName().toString())); + fields.add(new TypeStateBuilder.InitField(param.asType(), + param.getSimpleName().toString())); found = true; - } else if (mirror.getAnnotationType().asElement().getSimpleName() - .toString().equals("RequiredField")) { - if(found) { - processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", param); + } else if (mirror.getAnnotationType().asElement().getSimpleName().toString() + .equals("RequiredField")) { + if (found) { + processingEnv.getMessager().printMessage(Kind.ERROR, + "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", + param); } - fields.add(TypeStateBuilder.RequiredField.fromAnnotation(param.asType(), param.getSimpleName().toString(), mirror)); + fields.add(TypeStateBuilder.RequiredField.fromAnnotation(param.asType(), + param.getSimpleName().toString(), mirror)); found = true; - } else if (mirror.getAnnotationType().asElement().getSimpleName() - .toString().equals("OptionalField")) { - if(found) { - processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", param); + } else if (mirror.getAnnotationType().asElement().getSimpleName().toString() + .equals("OptionalField")) { + if (found) { + processingEnv.getMessager().printMessage(Kind.ERROR, + "Each parameter of a TypeStateBuilder constructor can only have one of @InitField, @RequiredField or @OptionalField", + param); } - fields.add(TypeStateBuilder.OptionalField.fromAnnotation(param.asType(), param.getSimpleName().toString(), mirror)); + fields.add(TypeStateBuilder.OptionalField.fromAnnotation(param.asType(), + param.getSimpleName().toString(), mirror)); found = true; } } - if(!found) { - processingEnv.getMessager().printMessage(Kind.ERROR, "Each parameter of a TypeStateBuilder constructor must have one of @InitField, @RequiredField or @OptionalField", param); + if (!found) { + processingEnv.getMessager().printMessage(Kind.ERROR, + "Each parameter of a TypeStateBuilder constructor must have one of @InitField, @RequiredField or @OptionalField", + param); } } - for(var field : fields) { + for (var field : fields) { // System.out.println("field " + field.name); - if(field instanceof TypeStateBuilder.MethodField method_field) { - if(method_field.alt != null) { - // System.out.println(" alt"); + if (field instanceof TypeStateBuilder.MethodField method_field) { + if (method_field.alt != null) { + // System.out.println(" alt"); } } } - var specBuilder = TypeSpec.classBuilder(builderName).addModifiers(Modifier.PUBLIC, Modifier.FINAL); + var specBuilder = + TypeSpec.classBuilder(builderName).addModifiers(Modifier.PUBLIC, Modifier.FINAL); - TypeStateBuilder typeStateBuilder = new TypeStateBuilder(builderName, fields.toArray(TypeStateBuilder.Field[]::new), parent.asType()); + TypeStateBuilder typeStateBuilder = new TypeStateBuilder(builderName, + fields.toArray(TypeStateBuilder.Field[]::new), parent.asType()); typeStateBuilder.apply(specBuilder); var spec = specBuilder.build(); @@ -142,9 +155,8 @@ private void processGenerateTypeStateBuilder(TypeElement annotation, RoundEnviro try { file.writeTo(processingEnv.getFiler()); } catch (IOException e) { - processingEnv - .getMessager() - .printMessage(Diagnostic.Kind.ERROR, "Failed to write class", constructorElement); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Failed to write class", constructorElement); e.printStackTrace(); } }); @@ -159,8 +171,8 @@ private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment rou List params = new ArrayList<>(); for (var mirror : classElement.getAnnotationMirrors()) { - if (!mirror.getAnnotationType().asElement().getSimpleName() - .toString().equals("GenerateEmptyIO")) { + if (!mirror.getAnnotationType().asElement().getSimpleName().toString() + .equals("GenerateEmptyIO")) { continue; } for (var ev : mirror.getElementValues().entrySet()) { @@ -171,25 +183,25 @@ private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment rou } var specBuilder = TypeSpec.classBuilder(emptyClassName) - .addSuperinterface( - TypeName.get(classElement.asType())) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + .addSuperinterface(TypeName.get(classElement.asType())) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); AtomicInteger i = new AtomicInteger(); var constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) - .addParameters(params.stream().map(ty -> { - return ParameterSpec.builder(TypeName.get(ty), "arg" + i.incrementAndGet()).build(); - }).toList()).build(); + .addParameters(params.stream().map(ty -> { + return ParameterSpec.builder(TypeName.get(ty), "arg" + i.incrementAndGet()) + .build(); + }).toList()).build(); for (var element : classElement.getEnclosedElements()) { if (element instanceof ExecutableElement javaMethod) { - specBuilder.addMethod(MethodSpec.methodBuilder(javaMethod.getSimpleName().toString()) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .returns(TypeName.VOID).addParameters(javaMethod.getParameters().stream().map(param -> { - return ParameterSpec - .builder(TypeName.get(param.asType()), param.getSimpleName().toString()) - .build(); + specBuilder + .addMethod(MethodSpec.methodBuilder(javaMethod.getSimpleName().toString()) + .addModifiers(Modifier.PUBLIC).addAnnotation(Override.class) + .returns(TypeName.VOID) + .addParameters(javaMethod.getParameters().stream().map(param -> { + return ParameterSpec.builder(TypeName.get(param.asType()), + param.getSimpleName().toString()).build(); }).toList()).addCode("// Intentionally do nothing").build()); } } @@ -202,9 +214,8 @@ private void processGenerateEmptyIO(TypeElement annotation, RoundEnvironment rou try { file.writeTo(processingEnv.getFiler()); } catch (IOException e) { - processingEnv - .getMessager() - .printMessage(Diagnostic.Kind.ERROR, "Failed to write class", classElement); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "Failed to write class", classElement); e.printStackTrace(); } }); diff --git a/src/main/java/org/frc5572/robotools/StringVisitor.java b/src/main/java/org/frc5572/robotools/StringVisitor.java index ed72122..1b87b3c 100644 --- a/src/main/java/org/frc5572/robotools/StringVisitor.java +++ b/src/main/java/org/frc5572/robotools/StringVisitor.java @@ -2,6 +2,7 @@ import javax.lang.model.util.SimpleAnnotationValueVisitor8; +/** Find all uses of a string in annotation values */ public class StringVisitor extends SimpleAnnotationValueVisitor8 { @Override diff --git a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java index 4f50918..e297119 100644 --- a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java +++ b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java @@ -1,25 +1,25 @@ package org.frc5572.robotools; import java.util.Arrays; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeMirror; - import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +/** Template builder for TypeState Builders */ public class TypeStateBuilder { private final Fields[] permutations; private final int num_permutable_fields; - public TypeStateBuilder(String name, Field[] fields_, - TypeMirror result) { - num_permutable_fields = (int) Arrays.stream(fields_).filter((x) -> !(x instanceof InitField)).count(); + /** Template builder for TypeState Builders */ + public TypeStateBuilder(String name, Field[] fields_, TypeMirror result) { + num_permutable_fields = + (int) Arrays.stream(fields_).filter((x) -> !(x instanceof InitField)).count(); permutations = new Fields[1 << num_permutable_fields]; boolean[] start = new boolean[num_permutable_fields]; Fields fields = new Fields(name, fields_, start, result); @@ -34,17 +34,18 @@ public TypeStateBuilder(String name, Field[] fields_, } } + /** Create typestate builders and write them to a typespec */ public void apply(TypeSpec.Builder builder) { Fields base = permutations[encodePermutationAsInt(new boolean[num_permutable_fields])]; base.write_fields(builder); base.write_constructor(builder, true); base.write_methods(builder, permutations); for (int i = 1; i < permutations.length; i++) { - if(permutations[i] == null) { + if (permutations[i] == null) { continue; } TypeSpec.Builder subBuilder = TypeSpec.classBuilder(permutations[i].class_name()) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL); permutations[i].write_fields(subBuilder); permutations[i].write_constructor(subBuilder, false); permutations[i].write_methods(subBuilder, permutations); @@ -62,50 +63,66 @@ private static boolean advance(boolean[] permutation) { return false; } + /** Base class for fields */ public static class Field { + /** Field type */ public final TypeMirror type; + /** Field name */ public final String name; + /** Base class for fields */ public Field(TypeMirror type, String name) { this.type = type; this.name = name; } } + /** Field that must be provided when creating a builder */ public static class InitField extends Field { + /** Field that must be provided when creating a builder */ public InitField(TypeMirror type, String name) { super(type, name); } } + /** A non-init field */ public static class MethodField extends Field { + /** An alternative method for fulfilling this field. */ public final AltMethod alt; + /** A non-init field */ public MethodField(TypeMirror type, String name, AltMethod alt) { super(type, name); this.alt = alt; } + /** A non-init field */ public MethodField(TypeMirror type, String name) { this(type, name, null); } } + /** A field that is required to finish the builder. */ public static class RequiredField extends MethodField { + /** A field that is required to finish the builder. */ public RequiredField(TypeMirror type, String name, AltMethod alt) { super(type, name, alt); } + /** A field that is required to finish the builder. */ public RequiredField(TypeMirror type, String name) { super(type, name); } - public static RequiredField fromAnnotation(TypeMirror type, String name, AnnotationMirror mirror) { + /** A field that is required to finish the builder. */ + public static RequiredField fromAnnotation(TypeMirror type, String name, + AnnotationMirror mirror) { AltMethod alt = null; for (var ev : mirror.getElementValues().entrySet()) { if (ev.getKey().getSimpleName().toString().equals("alt")) { - AnnotationMirror altMirror = ev.getValue().accept(new AnnotationMirrorVisitor(), null); + AnnotationMirror altMirror = + ev.getValue().accept(new AnnotationMirrorVisitor(), null); alt = AltMethod.fromAnnotation(altMirror, name); } } @@ -113,27 +130,35 @@ public static RequiredField fromAnnotation(TypeMirror type, String name, Annotat } } + + /** A field that has a default in case it is not specified. */ public static class OptionalField extends MethodField { + /** Java expression that provides the default value. */ public final String default_code; + /** A field that has a default in case it is not specified. */ public OptionalField(TypeMirror type, String name, AltMethod alt, String default_code) { super(type, name, alt); this.default_code = default_code; } + /** A field that has a default in case it is not specified. */ public OptionalField(TypeMirror type, String name, String default_code) { super(type, name); this.default_code = default_code; } - public static OptionalField fromAnnotation(TypeMirror type, String name, AnnotationMirror mirror) { + /** A field that has a default in case it is not specified. */ + public static OptionalField fromAnnotation(TypeMirror type, String name, + AnnotationMirror mirror) { String defaultCode = ""; AltMethod alt = null; for (var ev : mirror.getElementValues().entrySet()) { if (ev.getKey().getSimpleName().toString().equals("value")) { defaultCode = ev.getValue().accept(new StringVisitor(), null); } else if (ev.getKey().getSimpleName().toString().equals("alt")) { - AnnotationMirror altMirror = ev.getValue().accept(new AnnotationMirrorVisitor(), null); + AnnotationMirror altMirror = + ev.getValue().accept(new AnnotationMirrorVisitor(), null); alt = AltMethod.fromAnnotation(altMirror, name); } } @@ -141,17 +166,19 @@ public static OptionalField fromAnnotation(TypeMirror type, String name, Annotat } } + /** Alternative method for a field. */ public static record AltMethod(TypeMirror type, String parameterName, String code) { + /** Alternative method for a field. */ public static AltMethod fromAnnotation(AnnotationMirror mirror, String defaultName) { - if(mirror == null) { + if (mirror == null) { System.out.println("annotation is null"); return null; } - if (!mirror.getAnnotationType().asElement().getSimpleName() - .toString().equals("AltMethod")) { - System.out.println("annotation name doesn't match " + mirror.getAnnotationType().asElement().getSimpleName() - .toString()); + if (!mirror.getAnnotationType().asElement().getSimpleName().toString() + .equals("AltMethod")) { + System.out.println("annotation name doesn't match " + + mirror.getAnnotationType().asElement().getSimpleName().toString()); return null; } TypeMirror type = null; @@ -168,11 +195,11 @@ public static AltMethod fromAnnotation(AnnotationMirror mirror, String defaultNa } } - if(type == null) { + if (type == null) { System.out.println("Missing type"); return null; } - if(code == null) { + if (code == null) { System.out.println("Missing code"); return null; } @@ -181,7 +208,11 @@ public static AltMethod fromAnnotation(AnnotationMirror mirror, String defaultNa } } - public static record Fields(String name, Field[] fields, boolean[] permutation, TypeMirror result) { + + /** A specific permutation of fields. */ + public static record Fields(String name, Field[] fields, boolean[] permutation, + TypeMirror result) { + /** A unique class name */ public String class_name() { StringBuilder sb = new StringBuilder(name); for (int i = 0; i < permutation.length; i++) { @@ -190,10 +221,12 @@ public String class_name() { return sb.toString(); } + /** A unique class name */ public TypeName type_name() { return ClassName.get("", class_name()); } + /** True if all required fields are filled. */ public boolean could_finish() { int j = 0; for (int i = 0; i < fields.length; i++) { @@ -209,6 +242,7 @@ public boolean could_finish() { return true; } + /** True if all fields are filled. */ public boolean is_full() { for (int i = 0; i < permutation.length; i++) { if (!permutation[i]) { @@ -218,6 +252,7 @@ public boolean is_full() { return true; } + /** Get the permutation if a given index is additionally filled. */ public boolean[] next_permutation(int index) { boolean[] next_ = new boolean[permutation.length]; System.arraycopy(permutation, 0, next_, 0, permutation.length); @@ -225,9 +260,10 @@ public boolean[] next_permutation(int index) { return next_; } + /** Write constructor to typespec builder */ public void write_constructor(TypeSpec.Builder builder, boolean is_public) { var constructor = MethodSpec.constructorBuilder() - .addModifiers(is_public ? Modifier.PUBLIC : Modifier.PRIVATE); + .addModifiers(is_public ? Modifier.PUBLIC : Modifier.PRIVATE); int j = 0; for (int i = 0; i < fields.length; i++) { if (fields[i] instanceof MethodField) { @@ -235,12 +271,15 @@ public void write_constructor(TypeSpec.Builder builder, boolean is_public) { continue; } } - constructor.addParameter(TypeName.get(this.fields[i].type), this.fields[i].name + "_"); - constructor.addCode("this." + this.fields[i].name + "_ = " + this.fields[i].name + "_;\n"); + constructor.addParameter(TypeName.get(this.fields[i].type), + this.fields[i].name + "_"); + constructor + .addCode("this." + this.fields[i].name + "_ = " + this.fields[i].name + "_;\n"); } builder.addMethod(constructor.build()); } + /** Write fields to typespec builder */ public void write_fields(TypeSpec.Builder builder) { int j = 0; for (int i = 0; i < fields.length; i++) { @@ -249,15 +288,17 @@ public void write_fields(TypeSpec.Builder builder) { continue; } } - builder.addField(FieldSpec - .builder(TypeName.get(fields[i].type), fields[i].name + "_", Modifier.PRIVATE, Modifier.FINAL) - .build()); + builder.addField(FieldSpec.builder(TypeName.get(fields[i].type), + fields[i].name + "_", Modifier.PRIVATE, Modifier.FINAL).build()); } } - private void write_method(TypeSpec.Builder builder, int index, MethodField field, TypeName result) { - var method = MethodSpec.methodBuilder(field.name).addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(result) - .addParameter(TypeName.get(field.type), field.name + "_"); + /** Write method to typespec builder */ + private void write_method(TypeSpec.Builder builder, int index, MethodField field, + TypeName result) { + var method = + MethodSpec.methodBuilder(field.name).addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .returns(result).addParameter(TypeName.get(field.type), field.name + "_"); method.addCode("return new $T(", result); int j = 0; boolean is_first = true; @@ -283,15 +324,18 @@ private void write_method(TypeSpec.Builder builder, int index, MethodField field method.addCode(");"); builder.addMethod(method.build()); if (field.alt != null) { - method = MethodSpec.methodBuilder(field.name).addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(result).addParameter(TypeName.get(field.alt.type), - field.alt.parameterName); + method = MethodSpec.methodBuilder(field.name) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(result) + .addParameter(TypeName.get(field.alt.type), field.alt.parameterName); method.addCode("return this." + field.name + "(" + field.alt.code + ");"); builder.addMethod(method.build()); } } + /** Write method to complete builder to typespec builder */ private void write_finish(TypeSpec.Builder builder) { - var method = MethodSpec.methodBuilder("finish").addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(TypeName.get(result)); + var method = MethodSpec.methodBuilder("finish") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(TypeName.get(result)); method.addCode("return new $T(", TypeName.get(result)); int j = 0; for (int i = 0; i < fields.length; i++) { @@ -313,6 +357,7 @@ private void write_finish(TypeSpec.Builder builder) { builder.addMethod(method.build()); } + /** Write methods to advance builder to typespec builder */ public void write_methods(TypeSpec.Builder builder, Fields[] permutations) { int j = 0; for (int i = 0; i < this.fields.length; i++) {