Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
37e85df
adding explicit setter property in builder definition
AndreasIgel May 9, 2025
209e76f
Improving Javadoc
AndreasIgel May 9, 2025
6f4cccc
Renaming fields in BuilderDefinition for better understanding
AndreasIgel May 9, 2025
65a90d8
Adding fields of constructor to BuilderDefintion too
AndreasIgel May 9, 2025
0a00c54
Merge branch origin/main into feature/supporting-final-fields
AndreasIgel Aug 28, 2025
a8bdfa3
Updating tests according to desired functionality
AndreasIgel Aug 28, 2025
d341b42
Merge branch 'main' into feature/supporting-final-fields
AndreasIgel Sep 7, 2025
4dd4401
Update FieldDto.java
AndreasIgel Sep 7, 2025
389565f
Added annotation for selecting correct constructor
AndreasIgel Sep 11, 2025
95f1207
Adding todos for open tasks
AndreasIgel Sep 11, 2025
18f06dd
Merge branch 'feature/supporting-final-fields' of https://github.com/…
AndreasIgel Sep 13, 2025
07e0f64
Fixing codeformat
AndreasIgel Sep 13, 2025
26b1f1f
Removing methods without field-scope, because in this feature the inn…
AndreasIgel Sep 13, 2025
564a24d
adding a fallback in BuilderProcessor to handle cases, where simple-b…
AndreasIgel Sep 13, 2025
6ced7ef
Removing name for setter from FieldDto, this should be generated with…
AndreasIgel Sep 14, 2025
d592a53
Switching from one instance of target-type in builder to having all f…
AndreasIgel Sep 14, 2025
540ef7e
Removing codesmell
AndreasIgel Sep 14, 2025
9ecadce
Readding outcommented code
AndreasIgel Sep 14, 2025
affaf65
Removing field specific generics, because they prohibit generation of…
AndreasIgel Sep 14, 2025
3ead6ee
Adding a check for methods, which just start with "set" without being…
AndreasIgel Sep 14, 2025
37ad613
Improving tests to check that member variable is generated too
AndreasIgel Sep 14, 2025
a805f8d
Extracting getterName and only using it in constructor if there is a …
AndreasIgel Sep 24, 2025
94ca14f
Implementation of constructor fields extraction
AndreasIgel Sep 24, 2025
f9e15db
Adding javadoc for FieldDto
AndreasIgel Sep 24, 2025
247e834
Fixing codeformat
AndreasIgel Sep 24, 2025
78fbf6c
Checking behavior if constructor and setter is existing for a field
AndreasIgel Sep 24, 2025
47d6b53
Refactoring code with a helper function in BuilderDef, which provides…
AndreasIgel Sep 24, 2025
9559df4
Replacing rawType of ArrayList by generic one
AndreasIgel Oct 7, 2025
12719f1
Wrapp all fields into a record to decide on changed and unchanged values
AndreasIgel Oct 7, 2025
4103167
Adding a test for a class with non-empty constructor and final fields
AndreasIgel Oct 7, 2025
c625013
Adding JavaDoc
AndreasIgel Oct 7, 2025
9c874cf
Adding implementation for usage of SimpleBuilderConstructor annotation
AndreasIgel Oct 8, 2025
db88d68
Refactoring solution to improve code quality
AndreasIgel Oct 8, 2025
24ec529
Fixing generated javadoc
AndreasIgel Oct 8, 2025
cee289f
Merge branch 'main' into feature/supporting-final-fields
AndreasIgel Oct 8, 2025
3323a00
Adding javadoc generation on fields
AndreasIgel Oct 8, 2025
b74d00a
Improving tests
AndreasIgel Oct 8, 2025
23b5d30
Fixing implementation, looks like the solution wit comparing generics…
AndreasIgel Oct 8, 2025
98707b9
Fixing code format
AndreasIgel Oct 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* MIT License
*
* Copyright (c) 2025 Andreas Igel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.javahelpers.simple.builders.core.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to be placed on a constructor, if a specific constructor should be used for building
* the resulting DTO.
*/
@Target({ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface SimpleBuilderConstructor {}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,6 @@ public ArrayListBuilder<T> addAll(List<T> elements) {
* @return new ArrayList holding all elements which have been added before
*/
public List<T> build() {
return new ArrayList(mList);
return new ArrayList<>(mList);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* MIT License
*
* Copyright (c) 2025 Andreas Igel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.javahelpers.simple.builders.core.util;

import java.util.function.Consumer;

/**
* Represents a value together with a flag indicating whether it has been explicitly changed.
*
* <p>This utility is used by generated builders to differentiate between:
*
* <ul>
* <li>an unset value (no change made),
* <li>an initial value (derived from an existing instance), and
* <li>a changed value (explicitly set via a builder method).
* </ul>
*
* The {@code isChanged} flag controls whether setter invocations should be performed during {@code
* build()}; only changed values are applied.
*
* @param <T> the value type
* @param value the underlying value (may be {@code null})
* @param isChanged whether the value was explicitly changed by the builder API
*/
public record TrackedValue<T>(T value, boolean isChanged) {

/**
* Executes the provided {@link Consumer} only if this value has been explicitly changed.
*
* @param consumer action to perform with the current {@link #value()} when {@link #isChanged()}
* is {@code true}
*/
public void ifChanged(Consumer<T> consumer) {
if (isChanged) {
consumer.accept(value);
}
}

/**
* Creates a tracked value representing an unset state (no change made).
*
* @param <T> the value type
* @return an instance with {@code value == null} and {@code isChanged == false}
*/
public static <T> TrackedValue<T> unsetValue() {
return new TrackedValue<>(null, false);
}

/**
* Creates a tracked value representing an initial value copied from an existing instance. The
* value is present but is not considered a change initiated by the builder.
*
* @param <T> the value type
* @param value the initial value (may be {@code null})
* @return an instance with the given value and {@code isChanged == false}
*/
public static <T> TrackedValue<T> initialValue(T value) {
return new TrackedValue<>(value, false);
}

/**
* Creates a tracked value representing an explicit change performed via the builder API.
*
* @param <T> the value type
* @param value the changed value (may be {@code null})
* @return an instance with the given value and {@code isChanged == true}
*/
public static <T> TrackedValue<T> changedValue(T value) {
return new TrackedValue<>(value, true);
}
}
7 changes: 7 additions & 0 deletions processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
<auto-service.version>1.1.1</auto-service.version>
<javapoet.version>0.7.0</javapoet.version>
<commons-lang.version>3.19.0</commons-lang.version>
<commons-collections.version>4.4</commons-collections.version>
<junit-jupiter.version>5.13.4</junit-jupiter.version>
<google-compile-testing.version>0.23.0</google-compile-testing.version>

Expand Down Expand Up @@ -91,6 +92,12 @@
<artifactId>commons-lang3</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<!-- Utility functions for collections -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<!-- Utility for generate service file -->
<dependency>
<groupId>com.google.auto.service</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
import org.javahelpers.simple.builders.processor.exceptions.BuilderException;
import org.javahelpers.simple.builders.processor.util.JavaCodeGenerator;
Expand Down Expand Up @@ -88,7 +87,18 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
// Fail fast: we already emitted an error in init(); do not attempt any processing.
return true;
}
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(SimpleBuilder.class)) {
// Resolve annotation as TypeElement to support environments where the Class<?> overload
// of getElementsAnnotatedWith is unavailable.
TypeElement simpleBuilderAnnotation =
elementUtils.getTypeElement(
org.javahelpers.simple.builders.core.annotations.SimpleBuilder.class
.getCanonicalName());
if (simpleBuilderAnnotation == null) {
// TODO: Logging
// Annotation type not on classpath; nothing to do this round.
return true;
}
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(simpleBuilderAnnotation)) {
try {
process(annotatedElement);
} catch (BuilderException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,21 @@ public class BuilderDefinitionDto {
/** Type of builder. Containing package and name of DTO. */
private TypeName builderTypeName;

/** Generic parameters declared on the target DTO (e.g., <T extends Number, U>). */
private final List<GenericParameterDto> generics = new LinkedList<>();

/**
* List of all methods of target DTO which are supported by builder and not targeting a specific
* DTO field.
* Fields in constructor. Containing all fields which need to be given for calling the
* constructor. Ordering in LinkedList is matching orderung of parameters in constructor call.
* Instances of fields are added in LinkedList of all fields too.
*/
private final List<MethodDto> methodsForBuilder = new LinkedList<>();
private final List<FieldDto> fieldsInConstructor = new LinkedList<>();

/** Generic parameters declared on the target DTO (e.g., <T extends Number, U>). */
private final List<GenericParameterDto> generics = new LinkedList<>();

/**
* List of all fields of target DTO which are supported by builder. A field could be supported by
* multiple functions in builder.
*/
private final List<FieldDto> setterFieldsForBuilder = new LinkedList<>();
private final List<FieldDto> fields = new LinkedList<>();

/**
* Getting type of builder.
Expand Down Expand Up @@ -88,32 +89,18 @@ public void setBuildingTargetTypeName(TypeName buildingTargetTypeName) {
this.buildingTargetTypeName = buildingTargetTypeName;
}

/**
* Adding a method-definition to builder defintion.
*
* @param member {@code org.javahelpers.simple.builders.internal.dtos.MethodDto} to be added
*/
public void addMethod(MethodDto member) {
methodsForBuilder.add(member);
}

/**
* Getting all definied methods in builder definition.
*
* @return list of methods with type {@code
* org.javahelpers.simple.builders.internal.dtos.MethodDto}
*/
public List<MethodDto> getMethodsForBuilder() {
return methodsForBuilder;
}

/**
* Adding a field-definition to builder defintion.
*
* @param field {@code org.javahelpers.simple.builders.internal.dtos.FieldDto} to be added
*/
public void addField(FieldDto field) {
setterFieldsForBuilder.add(field);
fields.add(field);
}

/** Adds a field definition that will be provided via the DTO constructor. */
public void addFieldInConstructor(FieldDto field) {
fieldsInConstructor.add(field);
}

/**
Expand All @@ -122,7 +109,23 @@ public void addField(FieldDto field) {
* @return list of fields with type {@code org.javahelpers.simple.builders.internal.dtos.FieldDto}
*/
public List<FieldDto> getSetterFieldsForBuilder() {
return setterFieldsForBuilder;
return fields;
}

/** Returns the ordered list of fields to be passed to the DTO constructor. */
public List<FieldDto> getConstructorFieldsForBuilder() {
return fieldsInConstructor;
}

/**
* Returns all fields known to the builder in a single list. Constructor parameters appear first
* (in constructor order), followed by setter-derived fields.
*/
public List<FieldDto> getAllFieldsForBuilder() {
LinkedList<FieldDto> all = new LinkedList<>();
all.addAll(fieldsInConstructor);
all.addAll(fields);
return all;
}

/** Adds a generic parameter definition. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

/**
* Definition of a field. Containing all information to generate methods in builder to set or modify
Expand All @@ -38,7 +40,7 @@ public class FieldDto {
/** Type of field. Containing generic, name of package and class */
private TypeName fieldType;

/** List of all methods in builder, which change the field. */
/** List of all methods in builder, which provide helpers to change the field. */
private final List<MethodDto> fieldSetterMethodsList = new ArrayList<>();

/** Optional Javadoc of the field extracted from setter or constructor. */
Expand All @@ -47,6 +49,12 @@ public class FieldDto {
/** Method-level generics declared on the setter, to be reused in builder methods. */
private final List<GenericParameterDto> fieldGenerics = new ArrayList<>();

/**
* The name of the getter method to call, because in case of boolean it does not need to start
* with get (e.g., getName or isActive).
*/
private String getterName;

/**
* Getting name of field.
*
Expand All @@ -65,6 +73,15 @@ public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}

/**
* Getting name of setter method.
*
* @return name of setter for field
*/
public String getSetterName() {
return "set" + StringUtils.capitalize(fieldName);
}

/**
* Getting type of field.
*
Expand Down Expand Up @@ -132,4 +149,14 @@ public void setFieldGenerics(List<GenericParameterDto> generics) {
this.fieldGenerics.addAll(generics);
}
}

/** Returns the getter method name to use (without parentheses). */
public Optional<String> getGetterName() {
return Optional.ofNullable(getterName);
}

/** Sets the getter method name to use (without parentheses). */
public void setGetterName(String getterName) {
this.getterName = getterName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import static org.javahelpers.simple.builders.processor.dtos.MethodTypes.PROXY;

import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -52,9 +51,6 @@ public class MethodDto {
/** Definition of inner implementation for method. */
private final MethodCodeDto methodCodeDto = new MethodCodeDto();

/** Method-level type parameters (e.g., <T extends Serializable>). */
private final List<GenericParameterDto> methodGenerics = new LinkedList<>();

/**
* Getting type of method for source code generation. Defaults to {@code
* org.javahelpers.simple.builders.internal.dtos.MethodTypes.PROXY}.
Expand Down Expand Up @@ -163,19 +159,6 @@ public LinkedList<MethodParameterDto> getParameters() {
return parameters;
}

/** Replaces method-level generic parameter list with the provided entries. */
public void setMethodGenerics(List<GenericParameterDto> generics) {
this.methodGenerics.clear();
if (generics != null) {
this.methodGenerics.addAll(generics);
}
}

/** Returns declared method-level generic parameters. */
public List<GenericParameterDto> getMethodGenerics() {
return methodGenerics;
}

/**
* Getting the access modifier for method. Optional for usage in stream-notation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,14 @@ public String getClassName() {
public Optional<TypeName> getInnerType() {
return Optional.empty();
}

/**
* Creates a new instance of {@code TypeName} from an existing {@code Class}.
*
* @param clazz existing class to retrieve packe and class-name from
* @return
*/
public static TypeName of(Class<?> clazz) {
return new TypeName(clazz.getPackage().getName(), clazz.getSimpleName());
}
}
Loading
Loading