From 6eeb8f44059dd5cddc9fb78150cf1228aac91454 Mon Sep 17 00:00:00 2001 From: wilsonwatson Date: Fri, 6 Mar 2026 15:31:30 -0600 Subject: [PATCH 1/2] redo optionals --- .../org/frc5572/robotools/RobotProcessor.java | 9 - .../frc5572/robotools/TypeStateBuilder.java | 506 ++++++++++-------- 2 files changed, 282 insertions(+), 233 deletions(-) diff --git a/src/main/java/org/frc5572/robotools/RobotProcessor.java b/src/main/java/org/frc5572/robotools/RobotProcessor.java index 9516e75..d221642 100644 --- a/src/main/java/org/frc5572/robotools/RobotProcessor.java +++ b/src/main/java/org/frc5572/robotools/RobotProcessor.java @@ -133,15 +133,6 @@ private void processGenerateTypeStateBuilder(TypeElement annotation, } } - 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); diff --git a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java index e297119..2adb0f5 100644 --- a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java +++ b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java @@ -1,66 +1,308 @@ package org.frc5572.robotools; -import java.util.Arrays; +import java.util.ArrayList; + 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.ParameterSpec; 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; + private final String name; + private final ArrayList requiredFields = new ArrayList<>(); + private final ArrayList initFields = new ArrayList<>(); + private final ArrayList optionalFields = new ArrayList<>(); + private final Field[] fields; + private final TypeMirror result; /** 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); - while (true) { - int index = encodePermutationAsInt(start); - permutations[index] = fields; - start = Arrays.copyOf(start, start.length); - if (!advance(start)) { - break; + this.fields = fields_; + this.result = result; + this.name = name; + for (var field : fields_) { + if (field instanceof RequiredField rField) { + requiredFields.add(rField); + } else if (field instanceof OptionalField oField) { + optionalFields.add(oField); + } else if (field instanceof InitField iField) { + initFields.add(iField); } - fields = new Fields(name, fields_, start, result); } } + private static boolean advance(boolean[] enabled) { + for (int i = 0; i < enabled.length; i++) { + if (enabled[i]) { + enabled[i] = false; + } else { + enabled[i] = true; + return true; + } + } + return false; + } + /** 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) { - continue; + boolean[] enabled = new boolean[requiredFields.size()]; + + MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); + for (var field : initFields) { + builder.addField(FieldSpec + .builder(TypeName.get(field.type), field.name + "_", Modifier.PRIVATE, Modifier.FINAL).build()); + constructor.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + constructor.addCode("this." + field.name + "_ = " + field.name + "_;\n"); + } + for (var field : optionalFields) { + builder.addField(FieldSpec + .builder(TypeName.get(field.type), field.name + "_", Modifier.PRIVATE, Modifier.FINAL).build()); + constructor.addCode("this." + field.name + "_ = " + field.default_code + ";\n"); + } + builder.addMethod(constructor.addModifiers(Modifier.PUBLIC).build()); + constructor = MethodSpec.constructorBuilder(); + boolean isDifferent = false; + for (var field : initFields) { + constructor.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + constructor.addCode("this." + field.name + "_ = " + field.name + "_;\n"); + } + for (var field : optionalFields) { + constructor.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + constructor.addCode("this." + field.name + "_ = " + field.name + "_;\n"); + isDifferent = true; + } + if(isDifferent) { + builder.addMethod(constructor.addModifiers(Modifier.PRIVATE).build()); + } + addMethods(builder, enabled); + while (advance(enabled)) { + constructor = MethodSpec.constructorBuilder(); + for (int i = 0; i < enabled.length; i++) { + if (enabled[i]) { + var field = requiredFields.get(i); + constructor.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + constructor.addCode("this." + field.name + "_ = " + field.name + "_;\n"); + } } - 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()); + TypeSpec.Builder stepClass = TypeSpec.classBuilder(getName(enabled)); + for (int i = 0; i < enabled.length; i++) { + if (enabled[i]) { + var field = requiredFields.get(i); + stepClass.addField(FieldSpec + .builder(TypeName.get(field.type), field.name + "_", Modifier.PRIVATE, Modifier.FINAL) + .build()); + } + } + for (var field : initFields) { + stepClass.addField(FieldSpec + .builder(TypeName.get(field.type), field.name + "_", Modifier.PRIVATE, Modifier.FINAL).build()); + constructor.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + constructor.addCode("this." + field.name + "_ = " + field.name + "_;\n"); + } + for (var field : optionalFields) { + stepClass.addField(FieldSpec + .builder(TypeName.get(field.type), field.name + "_", Modifier.PRIVATE, Modifier.FINAL).build()); + constructor.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + constructor.addCode("this." + field.name + "_ = " + field.name + "_;\n"); + } + stepClass.addMethod(constructor.addModifiers(Modifier.PRIVATE).build()); + addMethods(stepClass, enabled); + builder.addType(stepClass.addModifiers(Modifier.PUBLIC).build()); } } - private static boolean advance(boolean[] permutation) { - for (int i = 0; i < permutation.length; i++) { - if (!permutation[i]) { - permutation[i] = true; - return true; + private String getName(boolean[] enabled) { + StringBuilder builderName = new StringBuilder(this.name); + boolean any = false; + for (int i = 0; i < enabled.length; i++) { + builderName.append(enabled[i] ? "1" : "0"); + any = any || enabled[i]; + } + if (any) { + return builderName.toString(); + } else { + return this.name; + } + } + + private void addMethods(TypeSpec.Builder builder, boolean[] enabled) { + boolean isFinishable = true; + for(int i = 0; i < enabled.length; i++) { + if(!enabled[i]) { + isFinishable = false; + break; + } + } + if(isFinishable) { + TypeName returnType = TypeName.get(result); + MethodSpec.Builder finish = MethodSpec.methodBuilder("finish").addModifiers(Modifier.PUBLIC).returns(returnType); + String code = "return new " + returnType + "("; + boolean isFirst = true; + for(Field field : fields) { + if(!isFirst) { + code += ", "; + } + code += field.name + "_"; + isFirst = false; + } + finish.addCode(code + ");\n"); + builder.addMethod(finish.build()); + } + for (int i = 0; i < enabled.length; i++) { + if (!enabled[i]) { + var field = requiredFields.get(i); + boolean[] next = new boolean[enabled.length]; + System.arraycopy(enabled, 0, next, 0, enabled.length); + next[i] = true; + String nextName = getName(next); + MethodSpec.Builder method = MethodSpec.methodBuilder(field.name).returns(ClassName.get("", nextName)); + method.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + String code = "return new " + nextName + "("; + boolean isFirst = true; + for (int j = 0; j < enabled.length; j++) { + if (enabled[j] || i == j) { + if (!isFirst) { + code += ", "; + } + code += requiredFields.get(j).name + "_"; + isFirst = false; + } + } + for (var field_ : initFields) { + if (!isFirst) { + code += ", "; + } + code += field_.name + "_"; + isFirst = false; + } + for (var field_ : optionalFields) { + if (!isFirst) { + code += ", "; + } + code += field_.name + "_"; + isFirst = false; + } + method.addCode(code + ");\n"); + builder.addMethod(method.addModifiers(Modifier.PUBLIC).build()); + + if (field.alt != null) { + method = MethodSpec.methodBuilder(field.name).returns(ClassName.get("", nextName)); + method.addParameter( + ParameterSpec.builder(TypeName.get(field.alt.type), field.alt.parameterName).build()); + code = "return new " + nextName + "("; + isFirst = true; + for (int j = 0; j < enabled.length; j++) { + if (i == j) { + if (!isFirst) { + code += ", "; + } + code += field.alt.code; + isFirst = false; + continue; + } + if (enabled[j]) { + if (!isFirst) { + code += ", "; + } + code += requiredFields.get(j).name + "_"; + isFirst = false; + } + } + for (var field_ : initFields) { + if (!isFirst) { + code += ", "; + } + code += field_.name + "_"; + isFirst = false; + } + for (var field_ : optionalFields) { + if (!isFirst) { + code += ", "; + } + code += field_.name + "_"; + isFirst = false; + } + method.addCode(code + ");\n"); + builder.addMethod(method.addModifiers(Modifier.PUBLIC).build()); + } + } + } + String thisName = getName(enabled); + for (var field : optionalFields) { + MethodSpec.Builder method = MethodSpec.methodBuilder(field.name).returns(ClassName.get("", thisName)); + method.addParameter(ParameterSpec.builder(TypeName.get(field.type), field.name + "_").build()); + String code = "return new " + thisName + "("; + boolean isFirst = true; + for (int i = 0; i < enabled.length; i++) { + if (enabled[i]) { + if (!isFirst) { + code += ", "; + } + code += requiredFields.get(i).name + "_"; + isFirst = false; + } + } + for (var field_ : initFields) { + if (!isFirst) { + code += ", "; + } + code += field_.name + "_"; + isFirst = false; + } + for (var field_ : optionalFields) { + if (!isFirst) { + code += ", "; + } + code += field_.name + "_"; + isFirst = false; + } + method.addCode(code + ");\n"); + builder.addMethod(method.addModifiers(Modifier.PUBLIC).build()); + + if(field.alt != null) { + method = MethodSpec.methodBuilder(field.name).returns(ClassName.get("", thisName)); + method.addParameter(ParameterSpec.builder(TypeName.get(field.alt.type), field.alt.parameterName).build()); + code = "return new " + thisName + "("; + isFirst = true; + for (int i = 0; i < enabled.length; i++) { + if (enabled[i]) { + if (!isFirst) { + code += ", "; + } + code += requiredFields.get(i).name + "_"; + isFirst = false; + } + } + for (var field_ : initFields) { + if (!isFirst) { + code += ", "; + } + code += field_.name + "_"; + isFirst = false; + } + for (var field_ : optionalFields) { + if (!isFirst) { + code += ", "; + } + if(field_ == field) { + code += field.alt.code; + } else { + code += field_.name + "_"; + } + isFirst = false; + } + method.addCode(code + ");\n"); + builder.addMethod(method.addModifiers(Modifier.PUBLIC).build()); } } - return false; } /** Base class for fields */ @@ -117,12 +359,11 @@ public RequiredField(TypeMirror type, String name) { /** A field that is required to finish the builder. */ public static RequiredField fromAnnotation(TypeMirror type, String name, - AnnotationMirror mirror) { + 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); } } @@ -130,7 +371,6 @@ public static RequiredField fromAnnotation(TypeMirror type, String name, } } - /** 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. */ @@ -150,15 +390,14 @@ public OptionalField(TypeMirror type, String name, String default_code) { /** A field that has a default in case it is not specified. */ public static OptionalField fromAnnotation(TypeMirror type, String name, - AnnotationMirror mirror) { + 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); } } @@ -176,9 +415,9 @@ public static AltMethod fromAnnotation(AnnotationMirror mirror, String defaultNa } if (!mirror.getAnnotationType().asElement().getSimpleName().toString() - .equals("AltMethod")) { + .equals("AltMethod")) { System.out.println("annotation name doesn't match " - + mirror.getAnnotationType().asElement().getSimpleName().toString()); + + mirror.getAnnotationType().asElement().getSimpleName().toString()); return null; } TypeMirror type = null; @@ -208,185 +447,4 @@ public static AltMethod fromAnnotation(AnnotationMirror mirror, String defaultNa } } - - /** 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++) { - sb.append(permutation[i] ? 1 : 0); - } - 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++) { - if (fields[i] instanceof MethodField) { - if (fields[i] instanceof RequiredField) { - if (!permutation[j]) { - return false; - } - } - j++; - } - } - return true; - } - - /** True if all fields are filled. */ - public boolean is_full() { - for (int i = 0; i < permutation.length; i++) { - if (!permutation[i]) { - return false; - } - } - 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); - next_[index] = true; - 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); - 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()); - } - - /** Write fields to typespec builder */ - 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()); - } - } - - /** 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; - 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()); - } - } - - /** 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)); - 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()); - } - - /** 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++) { - 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); - break; - } - 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 9ff253ccee3c0fdf2e7140cbb686721af3f41316 Mon Sep 17 00:00:00 2001 From: wilsonwatson Date: Fri, 6 Mar 2026 15:33:29 -0600 Subject: [PATCH 2/2] linting --- .../frc5572/robotools/TypeStateBuilder.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java index 2adb0f5..83cf1cf 100644 --- a/src/main/java/org/frc5572/robotools/TypeStateBuilder.java +++ b/src/main/java/org/frc5572/robotools/TypeStateBuilder.java @@ -79,7 +79,7 @@ public void apply(TypeSpec.Builder builder) { constructor.addCode("this." + field.name + "_ = " + field.name + "_;\n"); isDifferent = true; } - if(isDifferent) { + if (isDifferent) { builder.addMethod(constructor.addModifiers(Modifier.PRIVATE).build()); } addMethods(builder, enabled); @@ -135,19 +135,20 @@ private String getName(boolean[] enabled) { private void addMethods(TypeSpec.Builder builder, boolean[] enabled) { boolean isFinishable = true; - for(int i = 0; i < enabled.length; i++) { - if(!enabled[i]) { + for (int i = 0; i < enabled.length; i++) { + if (!enabled[i]) { isFinishable = false; break; } } - if(isFinishable) { + if (isFinishable) { TypeName returnType = TypeName.get(result); - MethodSpec.Builder finish = MethodSpec.methodBuilder("finish").addModifiers(Modifier.PUBLIC).returns(returnType); + MethodSpec.Builder finish = MethodSpec.methodBuilder("finish").addModifiers(Modifier.PUBLIC) + .returns(returnType); String code = "return new " + returnType + "("; boolean isFirst = true; - for(Field field : fields) { - if(!isFirst) { + for (Field field : fields) { + if (!isFirst) { code += ", "; } code += field.name + "_"; @@ -267,9 +268,10 @@ private void addMethods(TypeSpec.Builder builder, boolean[] enabled) { method.addCode(code + ");\n"); builder.addMethod(method.addModifiers(Modifier.PUBLIC).build()); - if(field.alt != null) { + if (field.alt != null) { method = MethodSpec.methodBuilder(field.name).returns(ClassName.get("", thisName)); - method.addParameter(ParameterSpec.builder(TypeName.get(field.alt.type), field.alt.parameterName).build()); + method.addParameter( + ParameterSpec.builder(TypeName.get(field.alt.type), field.alt.parameterName).build()); code = "return new " + thisName + "("; isFirst = true; for (int i = 0; i < enabled.length; i++) { @@ -292,7 +294,7 @@ private void addMethods(TypeSpec.Builder builder, boolean[] enabled) { if (!isFirst) { code += ", "; } - if(field_ == field) { + if (field_ == field) { code += field.alt.code; } else { code += field_.name + "_";