diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index e04cc6d..06229da 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,16 +18,16 @@ jobs: build: strategy: matrix: - java: [ '17', '21' ] + java: [ '11', '16', '17', '21', '22' ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up JDK - uses: actions/setup-java@v4 - with: - java-version: ${{ matrix.java }} - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -B -ntp verify --file pom.xml + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B -ntp verify --file pom.xml \ No newline at end of file diff --git a/pom.xml b/pom.xml index 66a9ada..44c6667 100644 --- a/pom.xml +++ b/pom.xml @@ -44,8 +44,8 @@ 1.5.0 3.1.2 - 17 - 17 + 11 + 11 1.9.0 1.13.0 diff --git a/processor/src/main/java/io/jonasg/bob/BuildableField.java b/processor/src/main/java/io/jonasg/bob/BuildableField.java index 84edfd3..41e2b6b 100644 --- a/processor/src/main/java/io/jonasg/bob/BuildableField.java +++ b/processor/src/main/java/io/jonasg/bob/BuildableField.java @@ -1,5 +1,6 @@ package io.jonasg.bob; +import java.util.Objects; import java.util.Optional; import javax.lang.model.type.TypeMirror; @@ -7,23 +8,84 @@ /** * Represents a field that is buildable * - * @param name the name of the field as declared in the type that will be built - * @param isConstructorArgument indicates if the field can be set through the constructor - * @param setterMethodName the name of the setter method to access the field. - * @param type the type of the field + * {@link BuildableField#name} the name of the field as declared in the type that will be built + * {@link BuildableField#isConstructorArgument} indicates if the field can be set through the constructor + * {@link BuildableField#setterMethodName} the name of the setter method to access the field. + * {@link BuildableField#type} the type of the field */ -public record BuildableField( - String name, - boolean isConstructorArgument, - boolean isMandatory, - Optional setterMethodName, - TypeMirror type) { - - public static BuildableField fromConstructor(String fieldName, TypeMirror type) { - return new BuildableField(fieldName, true, false, Optional.empty(), type); - } - - public static BuildableField fromSetter(String fieldName, boolean fieldIsMandatory, String setterMethodName, TypeMirror type) { - return new BuildableField(fieldName, false, fieldIsMandatory, Optional.of(setterMethodName), type); - } +public final class BuildableField { + private final String name; + private final boolean isConstructorArgument; + private final boolean isMandatory; + private final Optional setterMethodName; + private final TypeMirror type; + + public BuildableField( + String name, + boolean isConstructorArgument, + boolean isMandatory, + Optional setterMethodName, + TypeMirror type) { + this.name = name; + this.isConstructorArgument = isConstructorArgument; + this.isMandatory = isMandatory; + this.setterMethodName = setterMethodName; + this.type = type; + } + + public static BuildableField fromConstructor(String fieldName, TypeMirror type) { + return new BuildableField(fieldName, true, false, Optional.empty(), type); + } + + public static BuildableField fromSetter(String fieldName, boolean fieldIsMandatory, String setterMethodName, TypeMirror type) { + return new BuildableField(fieldName, false, fieldIsMandatory, Optional.of(setterMethodName), type); + } + + public String name() { + return name; + } + + public boolean isConstructorArgument() { + return isConstructorArgument; + } + + public boolean isMandatory() { + return isMandatory; + } + + public Optional setterMethodName() { + return setterMethodName; + } + + public TypeMirror type() { + return type; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (BuildableField) obj; + return Objects.equals(this.name, that.name) && + this.isConstructorArgument == that.isConstructorArgument && + this.isMandatory == that.isMandatory && + Objects.equals(this.setterMethodName, that.setterMethodName) && + Objects.equals(this.type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(name, isConstructorArgument, isMandatory, setterMethodName, type); + } + + @Override + public String toString() { + return "BuildableField[" + + "name=" + name + ", " + + "isConstructorArgument=" + isConstructorArgument + ", " + + "isMandatory=" + isMandatory + ", " + + "setterMethodName=" + setterMethodName + ", " + + "type=" + type + ']'; + } + } diff --git a/processor/src/main/java/io/jonasg/bob/BuilderTypeSpecFactory.java b/processor/src/main/java/io/jonasg/bob/BuilderTypeSpecFactory.java index 804d4e0..a876d27 100644 --- a/processor/src/main/java/io/jonasg/bob/BuilderTypeSpecFactory.java +++ b/processor/src/main/java/io/jonasg/bob/BuilderTypeSpecFactory.java @@ -65,11 +65,11 @@ protected BuilderTypeSpecFactory(TypeDefinition typeDefinition, Buildable builda private List extractBuildableFieldsFrom(TypeDefinition typeDefinition) { var fieldNames = typeDefinition.fields().stream() .map(FieldDefinition::name) - .toList(); + .collect(Collectors.toList()); List eligibleConstructorParams = this.constructorDefinition.parameters() .stream() .filter(p -> fieldNames.contains(p.name())) - .toList(); + .collect(Collectors.toList()); Stream constructorBuildableFields = this.constructorDefinition.parameters() .stream() .filter(p -> fieldNames.contains(p.name())) @@ -84,13 +84,13 @@ private List extractBuildableFieldsFrom(TypeDefinition typeDefin || p.field().isAnnotatedWith(Buildable.Mandatory.class); return BuildableField.fromSetter(p.field().name(), fieldIsMandatory, p.methodName(), p.type()); }); - return Stream.concat(constructorBuildableFields, setterBuildableFields).toList(); + return Stream.concat(constructorBuildableFields, setterBuildableFields).collect(Collectors.toList()); } private ConstructorDefinition extractConstructorDefinitionFrom(TypeDefinition typeDefinition) { var buildableConstructors = typeDefinition.constructors().stream() .filter(c -> c.isAnnotatedWith(Buildable.Constructor.class)) - .toList(); + .collect(Collectors.toList()); if (buildableConstructors.size() > 1) { throw new IllegalArgumentException("Only one constructor can be annotated with @Buildable.Constructor"); } @@ -132,7 +132,7 @@ private List generateSetters() { return this.buildableFields.stream() .filter(this::notExcluded) .map(this::generateSetterForField) - .toList(); + .collect(Collectors.toList()); } protected MethodSpec generateSetterForField(BuildableField field) { @@ -166,7 +166,7 @@ private boolean isEnforcedAllowNullsConstructorPolicy() { private List generateFields() { return buildableFields.stream() .map(this::generateField) - .toList(); + .collect(Collectors.toList()); } protected FieldSpec generateField(BuildableField field) { @@ -249,21 +249,27 @@ protected CodeBlock generateFieldAssignment(BuildableField field) { .build(); } else { return CodeBlock.builder() - .addStatement("instance.%s(this.%s)".formatted(setterName(field.setterMethodName().orElseThrow()), + .addStatement(String.format("instance.%s(this.%s)", setterName(field.setterMethodName().orElseThrow()), field.name())) .build(); } } protected String defaultForType(TypeMirror type) { - return switch (type.toString()) { - case "int" -> "0"; - case "long" -> "0L"; - case "float" -> "0.0f"; - case "double" -> "0.0d"; - case "boolean" -> "false"; - default -> "null"; - }; + switch (type.toString()) { + case "int": + return "0"; + case "long": + return "0L"; + case "float": + return "0.0f"; + case "double": + return "0.0d"; + case "boolean": + return "false"; + default: + return "null"; + } } private MethodSpec of() { diff --git a/processor/src/main/java/io/jonasg/bob/StepBuilderInterfaceTypeSpecFactory.java b/processor/src/main/java/io/jonasg/bob/StepBuilderInterfaceTypeSpecFactory.java index 07316f9..21cb155 100644 --- a/processor/src/main/java/io/jonasg/bob/StepBuilderInterfaceTypeSpecFactory.java +++ b/processor/src/main/java/io/jonasg/bob/StepBuilderInterfaceTypeSpecFactory.java @@ -7,8 +7,10 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import javax.lang.model.element.Modifier; @@ -40,9 +42,46 @@ public StepBuilderInterfaceTypeSpecFactory(TypeDefinition typeDefinition, this.packageName = packageName; } - record BuilderDetails(TypeSpec typeSpec, Set interfaces) { + final class BuilderDetails { + private final TypeSpec typeSpec; + private final Set interfaces; - } + BuilderDetails(TypeSpec typeSpec, Set interfaces) { + this.typeSpec = typeSpec; + this.interfaces = interfaces; + } + + public TypeSpec typeSpec() { + return typeSpec; + } + + public Set interfaces() { + return interfaces; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (BuilderDetails) obj; + return Objects.equals(this.typeSpec, that.typeSpec) && + Objects.equals(this.interfaces, that.interfaces); + } + + @Override + public int hashCode() { + return Objects.hash(typeSpec, interfaces); + } + + @Override + public String toString() { + return "BuilderDetails[" + + "typeSpec=" + typeSpec + ", " + + "interfaces=" + interfaces + ']'; + } + + + } BuilderDetails typeSpec(String builderImplName) { Set interfaces = new HashSet<>(); @@ -88,7 +127,7 @@ BuilderDetails typeSpec(String builderImplName) { .stream() .filter(field -> (field.isConstructorArgument() && isEnforcedConstructorPolicy()) || field.isMandatory()) - .toList(); + .collect(Collectors.toList()); mandatoryFields .subList(0, mandatoryFields.size() - 1) .stream() diff --git a/processor/src/main/java/io/jonasg/bob/TypeSpecInterfaceBuilder.java b/processor/src/main/java/io/jonasg/bob/TypeSpecInterfaceBuilder.java index dd7fe37..6fc8920 100644 --- a/processor/src/main/java/io/jonasg/bob/TypeSpecInterfaceBuilder.java +++ b/processor/src/main/java/io/jonasg/bob/TypeSpecInterfaceBuilder.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.lang.model.element.Modifier; @@ -84,7 +85,44 @@ public TypeSpec build() { } - record Argument(TypeName typeName, String name) { - } + final class Argument { + private final TypeName typeName; + private final String name; + + Argument(TypeName typeName, String name) { + this.typeName = typeName; + this.name = name; + } + + public TypeName typeName() { + return typeName; + } + + public String name() { + return name; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (Argument) obj; + return Objects.equals(this.typeName, that.typeName) && + Objects.equals(this.name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(typeName, name); + } + + @Override + public String toString() { + return "Argument[" + + "typeName=" + typeName + ", " + + "name=" + name + ']'; + } + + } } } diff --git a/processor/src/main/java/io/jonasg/bob/definitions/FieldDefinition.java b/processor/src/main/java/io/jonasg/bob/definitions/FieldDefinition.java index f220093..7f83a26 100644 --- a/processor/src/main/java/io/jonasg/bob/definitions/FieldDefinition.java +++ b/processor/src/main/java/io/jonasg/bob/definitions/FieldDefinition.java @@ -6,12 +6,58 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; -public record FieldDefinition(String name, - List annotations, - TypeMirror type) { - public boolean isAnnotatedWith(Class type) { - return annotations.stream() - .anyMatch(a -> Objects.equals(type.getName().replaceAll("\\$", "."), - a.getAnnotationType().asElement().asType().toString())); - } +public final class FieldDefinition { + private final String name; + private final List annotations; + private final TypeMirror type; + + public FieldDefinition(String name, + List annotations, + TypeMirror type) { + this.name = name; + this.annotations = annotations; + this.type = type; + } + + public boolean isAnnotatedWith(Class type) { + return annotations.stream() + .anyMatch(a -> Objects.equals(type.getName().replaceAll("\\$", "."), + a.getAnnotationType().asElement().asType().toString())); + } + + public String name() { + return name; + } + + public List annotations() { + return annotations; + } + + public TypeMirror type() { + return type; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (FieldDefinition) obj; + return Objects.equals(this.name, that.name) && + Objects.equals(this.annotations, that.annotations) && + Objects.equals(this.type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(name, annotations, type); + } + + @Override + public String toString() { + return "FieldDefinition[" + + "name=" + name + ", " + + "annotations=" + annotations + ", " + + "type=" + type + ']'; + } + } diff --git a/processor/src/main/java/io/jonasg/bob/definitions/MethodDefinition.java b/processor/src/main/java/io/jonasg/bob/definitions/MethodDefinition.java index 2350531..26193ae 100644 --- a/processor/src/main/java/io/jonasg/bob/definitions/MethodDefinition.java +++ b/processor/src/main/java/io/jonasg/bob/definitions/MethodDefinition.java @@ -1,9 +1,47 @@ package io.jonasg.bob.definitions; import java.util.List; +import java.util.Objects; import javax.lang.model.type.TypeMirror; -public record MethodDefinition(String name, - List parameters) { +public final class MethodDefinition { + private final String name; + private final List parameters; + + public MethodDefinition(String name, + List parameters) { + this.name = name; + this.parameters = parameters; + } + + public String name() { + return name; + } + + public List parameters() { + return parameters; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (MethodDefinition) obj; + return Objects.equals(this.name, that.name) && + Objects.equals(this.parameters, that.parameters); + } + + @Override + public int hashCode() { + return Objects.hash(name, parameters); + } + + @Override + public String toString() { + return "MethodDefinition[" + + "name=" + name + ", " + + "parameters=" + parameters + ']'; + } + } diff --git a/processor/src/main/java/io/jonasg/bob/definitions/SetterMethodDefinition.java b/processor/src/main/java/io/jonasg/bob/definitions/SetterMethodDefinition.java index 8165a86..ab65ea9 100644 --- a/processor/src/main/java/io/jonasg/bob/definitions/SetterMethodDefinition.java +++ b/processor/src/main/java/io/jonasg/bob/definitions/SetterMethodDefinition.java @@ -1,8 +1,54 @@ package io.jonasg.bob.definitions; import javax.lang.model.type.TypeMirror; +import java.util.Objects; + +public final class SetterMethodDefinition { + private final String methodName; + private final FieldDefinition field; + private final TypeMirror type; + + public SetterMethodDefinition(String methodName, + FieldDefinition field, + TypeMirror type) { + this.methodName = methodName; + this.field = field; + this.type = type; + } + + public String methodName() { + return methodName; + } + + public FieldDefinition field() { + return field; + } + + public TypeMirror type() { + return type; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (SetterMethodDefinition) obj; + return Objects.equals(this.methodName, that.methodName) && + Objects.equals(this.field, that.field) && + Objects.equals(this.type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(methodName, field, type); + } + + @Override + public String toString() { + return "SetterMethodDefinition[" + + "methodName=" + methodName + ", " + + "field=" + field + ", " + + "type=" + type + ']'; + } -public record SetterMethodDefinition(String methodName, - FieldDefinition field, - TypeMirror type) { } diff --git a/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinition.java b/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinition.java index b9dea65..d688589 100644 --- a/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinition.java +++ b/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinition.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * Defines a specific Java Type. @@ -53,7 +54,7 @@ public List getSetterMethods() { List setters = new ArrayList<>(); List methodsWithOneParam = this.methods.stream() .filter(m -> m.parameters().size() == 1) - .toList(); + .collect(Collectors.toList()); for (FieldDefinition field : fields) { String name = field.name().substring(0, 1).toUpperCase() + field.name().substring(1); methodsWithOneParam.stream() @@ -62,7 +63,7 @@ public List getSetterMethods() { .map(m -> new SetterMethodDefinition(m.name(), field, m.parameters().get(0))) .ifPresent(setters::add); methodsWithOneParam.stream() - .filter(m -> m.name().equals("set%s".formatted(name))) + .filter(m -> m.name().equals(String.format("set%s", name))) .findFirst() .map(m -> new SetterMethodDefinition(m.name(), field, m.parameters().get(0))) .ifPresent(setters::add); diff --git a/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinitionFactory.java b/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinitionFactory.java index bcbdca2..4115eef 100644 --- a/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinitionFactory.java +++ b/processor/src/main/java/io/jonasg/bob/definitions/TypeDefinitionFactory.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -141,12 +142,12 @@ private List fields() { private List methods() { return ElementFilter.methodsIn(element.getEnclosedElements()).stream() .map(e -> new MethodDefinition(e.getSimpleName().toString(), parameterTypes(e))) - .toList(); + .collect(Collectors.toList()); } private List parameterTypes(ExecutableElement element) { return element.getParameters().stream() .map(VariableElement::asType) - .toList(); + .collect(Collectors.toList()); } }