diff --git a/DEBUG_LOGGING.md b/DEBUG_LOGGING.md
new file mode 100644
index 00000000..2c3fe3c4
--- /dev/null
+++ b/DEBUG_LOGGING.md
@@ -0,0 +1,114 @@
+# Debug Logging for Simple Builders
+
+The Simple Builders annotation processor supports conditional debug logging that provides detailed information about the builder generation process.
+
+## Logging Levels
+
+- **INFO**: Always visible - Shows success messages for each builder generated
+- **WARNING**: Always visible - Shows when a builder cannot be generated (e.g., wrong annotation target, generation errors). Other builders will continue to be generated.
+- **ERROR**: Always visible - Shows fatal configuration errors (e.g., unsupported JDK version). Stops compilation completely.
+- **DEBUG**: Conditional - Shows detailed tracing of field discovery, method analysis, and code generation steps
+
+## Enabling Debug Logging
+
+Debug logging uses `Diagnostic.Kind.OTHER` but is only activated when explicitly enabled via the compiler argument `-Averbose=true`.
+
+### Option 1: Via Maven Property (Recommended)
+
+Add a property in your `pom.xml`:
+
+```xml
+
+ false
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+
+ -Averbose=${simplebuilder.verbose}
+
+
+
+
+
+```
+
+Then enable debug logging from command line:
+```bash
+mvn clean compile -Dsimplebuilder.verbose=true
+```
+
+### Option 2: Static Configuration
+
+Set it permanently in your `pom.xml`:
+
+```xml
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+
+ -Averbose=true
+
+
+
+
+
+```
+
+## Example Debug Output
+
+When debug logging is enabled, you'll see detailed output with visual separators:
+
+```
+[INFO] [DEBUG] ===============================
+[INFO] simple-builders: PROCESSING ROUND START
+[INFO] [DEBUG] ===============================
+[INFO] [DEBUG] simple-builders: Processing round started. Found 3 annotated elements.
+[INFO] [DEBUG] ------------------------------------
+[INFO] [DEBUG] simple-builders: Processing element: PersonDto
+[INFO] [DEBUG] ------------------------------------
+[INFO] [DEBUG] Extracting builder definition from: org.example.PersonDto
+[INFO] [DEBUG] Builder will be generated as: org.example.PersonDtoBuilder
+[INFO] [DEBUG] Analyzing method: setName with 1 parameter(s)
+[INFO] [DEBUG] -> Adding field: name (type: java.lang.String)
+[INFO] [DEBUG] Analyzing method: setAge with 1 parameter(s)
+[INFO] [DEBUG] -> Adding field: age (type: int)
+[INFO] [DEBUG] Processed 2 possible setters: added 2 fields, skipped 0
+[INFO] [DEBUG] Starting code generation for builder: PersonDtoBuilder
+[INFO] [DEBUG] Generating 0 constructor fields and 2 setter fields
+[INFO] [DEBUG] Generated 2 methods for field: name
+[INFO] [DEBUG] Generated 2 methods for field: age
+[INFO] [DEBUG] Writing builder class to file: org.example.PersonDtoBuilder
+[INFO] [DEBUG] Successfully generated builder: PersonDtoBuilder
+[INFO] simple-builders: Successfully generated builder for: PersonDto
+```
+
+**Note**: Debug messages are prefixed with `[DEBUG]` and use `Diagnostic.Kind.OTHER` which appears as `[INFO]` in Maven output.
+
+## Normal Output (Without Debug)
+
+Without debug logging enabled, you only see the INFO-level messages:
+
+```
+[INFO] simple-builders: Successfully generated builder for: PersonDto
+[INFO] simple-builders: Successfully generated builder for: OrderDto
+[INFO] simple-builders: Successfully generated builder for: CustomerDto
+```
+
+## Troubleshooting
+
+If debug logging is not appearing:
+
+1. Verify the compiler plugin configuration includes the debug or verbose argument
+2. Check that you're using Maven 3.6+
+3. Ensure the annotation processor is actually running (check for generated files in `target/generated-sources/annotations`)
+4. Try with `-X` flag to see all Maven debug output and verify the compiler arguments are being passed
diff --git a/README.md b/README.md
index 6b74fc7e..cc5823a8 100644
--- a/README.md
+++ b/README.md
@@ -16,9 +16,11 @@
- [Usage](#usage)
- [Basic Usage](#basic-usage)
- [Collections and Nested Objects](#collections-and-nested-objects)
+ - [Debug Logging](#debug-logging)
- [Building from Source](#building-from-source)
- [Contributing](#contributing)
- [License](#license)
+- [Links](#links)
## What is Simple Builders?
@@ -169,6 +171,12 @@ Project project = ProjectBuilder.create()
.build();
```
+### Debug Logging
+
+Simple Builders supports detailed debug logging to trace the builder generation process. Enable it with the `-Averbose=true` compiler argument for detailed insights into field discovery, method analysis, and code generation.
+
+For complete documentation on enabling and using debug logging, see [DEBUG_LOGGING.md](DEBUG_LOGGING.md).
+
## Building from Source
1. Clone the repository:
@@ -210,7 +218,3 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
* [Downloads](https://github.com/java-helpers/simple-builders/releases)
* [Issue tracker](https://github.com/java-helpers/simple-builders/issues)
* [CI build](https://github.com/java-helpers/simple-builders/actions/)
-
-## Licensing
-
-SimpleBuilders is licensed under the MIT License; you may not use this project except in compliance with the License. You may obtain a copy of the License at http://www.opensource.org/licenses/mit-license.php.
diff --git a/example/pom.xml b/example/pom.xml
index 1aba9198..e7c618db 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -24,6 +24,9 @@
${java.version}
${java.version}
${java.version}
+
+
+ false
@@ -77,6 +80,9 @@
${project.version}
+
+ -Averbose=${simplebuilder.verbose}
+
diff --git a/example/src/main/java/org/javahelpers/simple/builders/example/MannschaftDto.java b/example/src/main/java/org/javahelpers/simple/builders/example/MannschaftDto.java
index 93d170e5..185f3cc6 100644
--- a/example/src/main/java/org/javahelpers/simple/builders/example/MannschaftDto.java
+++ b/example/src/main/java/org/javahelpers/simple/builders/example/MannschaftDto.java
@@ -25,6 +25,9 @@
package org.javahelpers.simple.builders.example;
import java.util.Set;
+import java.util.function.Supplier;
+
+import org.javahelpers.simple.builders.core.annotations.IgnoreInBuilder;
import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
@SimpleBuilder
@@ -40,6 +43,11 @@ public void setName(String name) {
this.name = name;
}
+ @IgnoreInBuilder
+ public void setName(Supplier nameSupplier) {
+ this.name = nameSupplier.get();
+ }
+
public Set getSponsoren() {
return sponsoren;
}
diff --git a/example/src/main/java/org/javahelpers/simple/builders/example/PersonDto.java b/example/src/main/java/org/javahelpers/simple/builders/example/PersonDto.java
index 16c4bd31..77faf11a 100644
--- a/example/src/main/java/org/javahelpers/simple/builders/example/PersonDto.java
+++ b/example/src/main/java/org/javahelpers/simple/builders/example/PersonDto.java
@@ -30,18 +30,18 @@
@SimpleBuilder
public class PersonDto {
- private String name;
+ private final String name;
private int groesse;
private List nickNames;
private java.time.LocalDate birthdate;
private MannschaftDto mannschaft;
- public String getName() {
- return name;
+ public PersonDto(String name) {
+ this.name = name;
}
- public void setName(String name) {
- this.name = name;
+ public String getName() {
+ return name;
}
public int getGroesse() {
diff --git a/example/src/main/java/org/javahelpers/simple/builders/example/goal/PersonDtoBuilder.java b/example/src/main/java/org/javahelpers/simple/builders/example/goal/PersonDtoBuilder.java
index e339a500..4fa03f54 100644
--- a/example/src/main/java/org/javahelpers/simple/builders/example/goal/PersonDtoBuilder.java
+++ b/example/src/main/java/org/javahelpers/simple/builders/example/goal/PersonDtoBuilder.java
@@ -33,14 +33,13 @@
@Generated("org.javahelpers.simple.builders.processor")
@BuilderImplementation(forClass = PersonDto.class)
public class PersonDtoBuilder {
- private final PersonDto instance = new PersonDto();
+ private final PersonDto instance = new PersonDto("test");
public static PersonDtoBuilder create() {
return new PersonDtoBuilder();
}
public PersonDtoBuilder name(String name) {
- instance.setName(name);
return this;
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java
index d1b38b24..50c63021 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java
@@ -33,6 +33,7 @@
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@@ -49,6 +50,7 @@
*/
@AutoService(Processor.class)
@SupportedAnnotationTypes("org.javahelpers.simple.builders.core.annotations.SimpleBuilder")
+@SupportedOptions("verbose")
public class BuilderProcessor extends AbstractProcessor {
private ProcessingContext context;
private JavaCodeGenerator codeGenerator;
@@ -57,20 +59,17 @@ public class BuilderProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
- ProcessingLogger logger = new ProcessingLogger(processingEnv.getMessager());
+ ProcessingLogger logger = new ProcessingLogger(processingEnv);
this.context =
new ProcessingContext(
processingEnv.getElementUtils(), processingEnv.getTypeUtils(), logger);
- this.codeGenerator = new JavaCodeGenerator(processingEnv.getFiler());
-
- // Enforce minimum Java version (17+) for the processor
+ this.codeGenerator = new JavaCodeGenerator(processingEnv.getFiler(), logger);
SourceVersion current = processingEnv.getSourceVersion();
this.supportedJdk = isAtLeastJava17(current);
if (!this.supportedJdk) {
context.error(
- "simple-builders requires Java 17 or higher for annotation processing. Detected: "
- + current
- + ". Please upgrade to JDK 17+ or disable the processor.");
+ "simple-builders requires Java 17 or higher for annotation processing. Detected: '%s'. Please upgrade to JDK 17+ or disable the processor.",
+ current);
}
}
@@ -89,13 +88,30 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
if (simpleBuilderAnnotation == null) {
context.error(
"Annotation org.javahelpers.simple.builders.core.annotations.SimpleBuilder is not on classpath. So nothing to do here.");
- return false;
}
- for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(simpleBuilderAnnotation)) {
+
+ Set extends Element> annotatedElements =
+ roundEnv.getElementsAnnotatedWith(simpleBuilderAnnotation);
+ context.debug("===============================");
+ context.info("simple-builders: PROCESSING ROUND START");
+ context.debug("===============================");
+ context.debug(
+ "simple-builders: Processing round started. Found %d annotated elements.",
+ annotatedElements.size());
+
+ for (Element annotatedElement : annotatedElements) {
try {
+ context.debug("------------------------------------");
+ context.debug("simple-builders: Processing element: %s", annotatedElement.getSimpleName());
+ context.debug("------------------------------------");
process(annotatedElement);
+ context.info(
+ "simple-builders: Successfully generated builder for: %s",
+ annotatedElement.getSimpleName());
} catch (BuilderException ex) {
- context.error(annotatedElement, "Failed to process annotated element: " + ex.getMessage());
+ // All builder generation failures are warnings to allow other builders to be generated
+ context.warning(
+ annotatedElement, "simple-builders: Failed to generate builder - %s", ex.getMessage());
}
}
return true;
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java
index f8d54a84..05d8f767 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java
@@ -103,6 +103,16 @@ public void addFieldInConstructor(FieldDto field) {
fieldsInConstructor.add(field);
}
+ /** Adds multiple field definitions to be provided via the DTO constructor. */
+ public void addAllFieldsInConstructor(List fields) {
+ fieldsInConstructor.addAll(fields);
+ }
+
+ /** Adds multiple field definitions to the builder. */
+ public void addAllFields(List fields) {
+ this.fields.addAll(fields);
+ }
+
/**
* Getting all definied fields in builder definition.
*
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java
index 6df46c67..d6b78afa 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java
@@ -32,6 +32,7 @@
import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.*;
+import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -39,7 +40,6 @@
import java.util.function.Supplier;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementFilter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
@@ -72,11 +72,11 @@ private BuilderDefinitionCreator() {
}
/**
- * Extracting definition for a builder from annotated element.
+ * Extracts a BuilderDefinition from the annotated element.
*
- * @param annotatedElement annotated elment which is target of builder creation
- * @param context processing context with element and type utilities
- * @return definition of builder
+ * @param annotatedElement the annotated type element to extract the builder definition from
+ * @param context the processing context for annotations processing
+ * @return the builder definition
* @throws BuilderException if validation or generation failed
*/
public static BuilderDefinitionDto extractFromElement(
@@ -84,28 +84,73 @@ public static BuilderDefinitionDto extractFromElement(
validateAnnotatedElement(annotatedElement);
TypeElement annotatedType = (TypeElement) annotatedElement;
+ context.debug("Extracting builder definition from: %s", annotatedType.getQualifiedName());
+
+ BuilderDefinitionDto result = initializeBuilderDefinition(annotatedType, context);
+
+ List constructorFields = extractConstructorFields(annotatedType, context);
+ result.addAllFieldsInConstructor(constructorFields);
+
+ List setterFields = extractSetterFields(annotatedType, result, context);
+ result.addAllFields(setterFields);
+
+ return result;
+ }
+
+ /** Initializes the builder definition with package, class name, and generics. */
+ private static BuilderDefinitionDto initializeBuilderDefinition(
+ TypeElement annotatedType, ProcessingContext context) {
BuilderDefinitionDto result = new BuilderDefinitionDto();
String packageName = context.getPackageName(annotatedType);
String simpleClassName = annotatedType.getSimpleName().toString();
result.setBuilderTypeName(new TypeName(packageName, simpleClassName + BUILDER_SUFFIX));
result.setBuildingTargetTypeName(new TypeName(packageName, simpleClassName));
+ context.debug(
+ "Builder will be generated as: %s.%s", packageName, simpleClassName + BUILDER_SUFFIX);
+
// Extract generics from the annotated type via mapper (stream-based)
JavaLangMapper.map2GenericParameterDtos(annotatedType, context).forEach(result::addGeneric);
- // Extract constructors and their parameters (treat as constructor fields)
+ return result;
+ }
+
+ /**
+ * Extracts fields from the constructor parameters.
+ *
+ * @return list of fields extracted from constructor parameters
+ */
+ private static List extractConstructorFields(
+ TypeElement annotatedType, ProcessingContext context) {
+ List constructorFields = new LinkedList<>();
Optional constructorOpt = findConstructorForBuilder(annotatedType, context);
if (constructorOpt.isPresent()) {
ExecutableElement ctor = constructorOpt.get();
+ context.debug(
+ "Analyzing constructor: %s with %d parameter(s)",
+ ctor.getSimpleName(), ctor.getParameters().size());
for (VariableElement param : ctor.getParameters()) {
Optional fieldFromCtor =
createFieldFromConstructor(annotatedType, param, context);
- fieldFromCtor.ifPresent(result::addFieldInConstructor);
+ if (fieldFromCtor.isPresent()) {
+ FieldDto field = fieldFromCtor.get();
+ logFieldAddition(field, context);
+ constructorFields.add(field);
+ }
}
}
+ return constructorFields;
+ }
- List extends Element> allMembers = context.getAllMembers(annotatedType);
- List methods = ElementFilter.methodsIn(allMembers);
+ /**
+ * Extracts fields from setter methods, avoiding duplicates from constructor fields.
+ *
+ * @param result the builder definition containing constructor fields to check for duplicates
+ * @return list of fields extracted from setter methods
+ */
+ private static List extractSetterFields(
+ TypeElement annotatedType, BuilderDefinitionDto result, ProcessingContext context) {
+ List setterFields = new LinkedList<>();
// Build a set of constructor field names to avoid duplicates from setters
Set ctorFieldNames =
@@ -113,31 +158,74 @@ public static BuilderDefinitionDto extractFromElement(
.map(FieldDto::getFieldName)
.collect(toSet());
+ List methods = findAllPossibleSettersOfClass(annotatedType, context);
+ int processedCount = 0;
+ int addedCount = 0;
+ int skippedCount = 0;
+
for (ExecutableElement mth : methods) {
- // nur public
- if (isMethodRelevantForBuilder(mth)) {
- // Avoid adding setter-derived fields that duplicate constructor params
+ context.debug(
+ "Analyzing method: %s with %d parameter(s)",
+ mth.getSimpleName(), mth.getParameters().size());
+
+ if (isMethodRelevantForBuilder(mth, context)) {
Optional maybeField = createFieldFromSetter(mth, context);
if (maybeField.isPresent()) {
+ processedCount++;
FieldDto field = maybeField.get();
if (!ctorFieldNames.contains(field.getFieldName())) {
- result.addField(field);
+ addedCount++;
+ logFieldAddition(field, context);
+ setterFields.add(field);
+ continue;
}
+ } else {
+ context.debug(" -> Skipping method (already in constructor): %s", mth.getSimpleName());
}
}
+ skippedCount++;
}
- return result;
+ context.debug(
+ "Processed %d possible setters: added %d fields, skipped %d",
+ processedCount, addedCount, skippedCount);
+
+ return setterFields;
}
- private static boolean isMethodRelevantForBuilder(ExecutableElement mth) {
- return isSetterForField(mth)
- && isNoMethodOfObjectClass(mth)
- && hasNoThrowablesDeclared(mth)
- && hasNoReturnValue(mth)
- && hasNotAnnotation(IgnoreInBuilder.class, mth)
- && isNotPrivate(mth)
- && isNotStatic(mth);
+ /** Logs the addition of a field with its type information. */
+ private static void logFieldAddition(FieldDto field, ProcessingContext context) {
+ String fieldTypeName = field.getFieldType().getClassName();
+ if (field.getFieldType().getPackageName() != null
+ && !field.getFieldType().getPackageName().isEmpty()) {
+ fieldTypeName = field.getFieldType().getPackageName() + "." + fieldTypeName;
+ }
+ context.debug(" -> Adding field: %s (type: %s)", field.getFieldName(), fieldTypeName);
+ }
+
+ private static boolean isMethodRelevantForBuilder(
+ ExecutableElement mth, ProcessingContext context) {
+ if (!hasNoThrowablesDeclared(mth)) {
+ context.debug(" -> Skipping: declares throwables");
+ return false;
+ }
+ if (!hasNoReturnValue(mth)) {
+ context.debug(" -> Skipping: has return value");
+ return false;
+ }
+ if (!hasNotAnnotation(IgnoreInBuilder.class, mth)) {
+ context.debug(" -> Skipping: has @IgnoreInBuilder annotation");
+ return false;
+ }
+ if (!isNotPrivate(mth)) {
+ context.debug(" -> Skipping: is private");
+ return false;
+ }
+ if (!isNotStatic(mth)) {
+ context.debug(" -> Skipping: is static");
+ return false;
+ }
+ return true;
}
private static void addAdditionalHelperMethodsForField(
@@ -239,43 +327,12 @@ private static Optional createFieldFromSetter(
String methodName = mth.getSimpleName().toString();
String fieldName = StringUtils.uncapitalize(Strings.CI.removeStart(methodName, "set"));
- FieldDto result = new FieldDto();
- result.setFieldName(fieldName);
List extends VariableElement> parameters = mth.getParameters();
if (parameters.size() != 1) {
// Should never happen, just to be sure here
- context.warning(
- mth.getEnclosingElement(),
- "Setter method " + methodName + " has " + parameters.size() + " parameters, expected 1");
+ context.warning(mth, "Unexpected state of method.");
return Optional.empty();
}
- VariableElement fieldParameter = parameters.get(0);
- TypeMirror fieldTypeMirror = fieldParameter.asType();
- Element rawElement = context.asElement(fieldTypeMirror);
- TypeElement fieldTypeElement = rawElement instanceof TypeElement te ? te : null;
-
- // Extract only the @param Javadoc for the single setter parameter (if present)
- String fullJavaDoc = context.getDocComment(mth);
- String parameterJavaDocExtracted =
- JavaLangAnalyser.extractParamJavaDoc(fullJavaDoc, fieldParameter);
- String parameterJavaDoc =
- parameterJavaDocExtracted == null ? fieldName : parameterJavaDocExtracted;
- result.setJavaDoc(parameterJavaDoc);
-
- // extracting type of field
- MethodParameterDto fieldParameterDto = map2MethodParameter(fieldParameter, context);
- if (fieldParameterDto == null) {
- context.warning(mth.getEnclosingElement(), "Could not extract type of field " + fieldName);
- return Optional.empty();
- }
- TypeName fieldType = fieldParameterDto.getParameterType();
- result.setFieldType(fieldType);
-
- // Determine if a corresponding getter exists on the DTO and record its name
- TypeElement dtoType = (TypeElement) mth.getEnclosingElement();
- JavaLangAnalyser.findGetterForField(dtoType, fieldName, fieldTypeMirror, context)
- .ifPresent(
- getterExecutable -> result.setGetterName(getterExecutable.getSimpleName().toString()));
// Finding generics declared on the setter itself (field-specific), e.g.,
@@ -284,20 +341,22 @@ private static Optional createFieldFromSetter(
if (CollectionUtils.isNotEmpty(mth.getTypeParameters())) {
context.warning(
mth.getEnclosingElement(),
- "Field " + fieldName + " has field-specific generics, so it will be ignored");
+ "Field '%s' has field-specific generics, so it will be ignored",
+ fieldName);
return Optional.empty();
}
- // simple setter
- result.addMethod(createFieldSetterWithTransform(fieldName, null, fieldType));
+ VariableElement fieldParameter = parameters.get(0);
+ TypeElement dtoType = (TypeElement) mth.getEnclosingElement();
- // add consumer/supplier generation via helpers
- addConsumerMethodsForField(
- result, fieldName, fieldType, fieldParameter, fieldTypeElement, context);
- addSupplierMethodsForField(result, fieldName, fieldType, fieldTypeElement);
- addAdditionalHelperMethodsForField(result, fieldName, fieldType);
+ // Extract only the @param Javadoc for the single setter parameter (if present)
+ String fullJavaDoc = context.getDocComment(mth);
+ String javaDoc = JavaLangAnalyser.extractParamJavaDoc(fullJavaDoc, fieldParameter);
+ if (javaDoc == null) {
+ javaDoc = fieldName;
+ }
- return Optional.of(result);
+ return createFieldDto(fieldName, javaDoc, fieldParameter, dtoType, context);
}
/**
@@ -306,23 +365,60 @@ private static Optional createFieldFromSetter(
*/
private static Optional createFieldFromConstructor(
TypeElement dtoType, VariableElement param, ProcessingContext context) {
+ String fieldName = param.getSimpleName().toString();
+ // Set javadoc (default to field name if no javadoc found)
+ String javaDoc = JavaLangAnalyser.extractParamJavaDoc(context.getDocComment(dtoType), param);
+ if (javaDoc == null) {
+ javaDoc = fieldName;
+ }
+ return createFieldDto(fieldName, javaDoc, param, dtoType, context);
+ }
+
+ /**
+ * Common method to create a FieldDto with all builder methods (setter, supplier, consumer,
+ * helpers).
+ *
+ * @param fieldName the name of the field
+ * @param javaDoc the javadoc for the field
+ * @param param the parameter element (from constructor or setter)
+ * @param dtoType the DTO type containing this field
+ * @param context processing context
+ * @return Optional containing the FieldDto, or empty if field cannot be created
+ */
+ private static Optional createFieldDto(
+ String fieldName,
+ String javaDoc,
+ VariableElement param,
+ TypeElement dtoType,
+ ProcessingContext context) {
MethodParameterDto paramDto = map2MethodParameter(param, context);
if (paramDto == null) {
return Optional.empty();
}
- String fieldName = param.getSimpleName().toString();
+
TypeName fieldType = paramDto.getParameterType();
- FieldDto ctorField = new FieldDto();
- ctorField.setFieldName(fieldName);
- ctorField.setFieldType(fieldType);
- // Set javadoc (default to field name if no javadoc found)
- ctorField.setJavaDoc(fieldName);
+ TypeMirror fieldTypeMirror = param.asType();
+ Element rawElement = context.asElement(fieldTypeMirror);
+ TypeElement fieldTypeElement = rawElement instanceof TypeElement te ? te : null;
+
+ FieldDto field = new FieldDto();
+ field.setFieldName(fieldName);
+ field.setFieldType(fieldType);
+ field.setJavaDoc(javaDoc);
+
// Find matching getter on the DTO type
- JavaLangAnalyser.findGetterForField(dtoType, fieldName, param.asType(), context)
- .ifPresent(getter -> ctorField.setGetterName(getter.getSimpleName().toString()));
- // Provide a simple setter in builder to set the constructor argument
- ctorField.addMethod(createFieldSetterWithTransform(fieldName, null, fieldType));
- return Optional.of(ctorField);
+ JavaLangAnalyser.findGetterForField(dtoType, fieldName, fieldTypeMirror, context)
+ .ifPresent(getter -> field.setGetterName(getter.getSimpleName().toString()));
+
+ // Add basic setter method
+ field.addMethod(createFieldSetterWithTransform(fieldName, null, fieldType));
+
+ // Add consumer/supplier/helper methods
+ addConsumerMethodsForField(field, fieldName, fieldType, param, fieldTypeElement, context);
+ addSupplierMethodsForField(field, fieldName, fieldType, fieldTypeElement);
+ addAdditionalHelperMethodsForField(field, fieldName, fieldType);
+
+ return Optional.of(field);
}
private static MethodDto createFieldSetterWithTransform(
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaCodeGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaCodeGenerator.java
index 59620ea4..6ae80bbe 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaCodeGenerator.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaCodeGenerator.java
@@ -52,24 +52,31 @@ public class JavaCodeGenerator {
/** Util class for source code generation of type {@code javax.annotation.processing.Filer}. */
private final Filer filer;
+ /** Logger for debug output during code generation. */
+ private final ProcessingLogger logger;
+
/**
* Constructor for JavaCodeGenerator.
*
* @param filer Util class for source code generation of type {@code
* javax.annotation.processing.Filer}
+ * @param logger Logger for debug output
*/
- public JavaCodeGenerator(Filer filer) {
+ public JavaCodeGenerator(Filer filer, ProcessingLogger logger) {
this.filer = filer;
+ this.logger = logger;
}
/**
* Generating source code file for builder. Creation of source code files is done by {@code
- * javax.annotation.processing.Filer}.
*
* @param builderDef dto of all information to create the builder
* @throws BuilderException if there is an error in source code generation
*/
public void generateBuilder(BuilderDefinitionDto builderDef) throws BuilderException {
+ logger.debug(
+ "Starting code generation for builder: %s", builderDef.getBuilderTypeName().getClassName());
+
ClassName builderBaseClass = map2ClassName(builderDef.getBuilderTypeName());
ClassName dtoBaseClass = map2ClassName(builderDef.getBuildingTargetTypeName());
com.palantir.javapoet.TypeName builderTypeName;
@@ -83,6 +90,7 @@ public void generateBuilder(BuilderDefinitionDto builderDef) throws BuilderExcep
dtoTypeName =
map2ParameterizedTypeName(
builderDef.getBuildingTargetTypeName(), builderDef.getGenerics());
+ logger.debug("Builder has %d generic type parameter(s)", builderDef.getGenerics().size());
}
TypeSpec.Builder classBuilder =
@@ -97,6 +105,11 @@ public void generateBuilder(BuilderDefinitionDto builderDef) throws BuilderExcep
dtoBaseClass, dtoTypeName, builderDef.getAllFieldsForBuilder()));
classBuilder.addMethod(createEmptyConstructor(dtoBaseClass));
+ logger.debug(
+ "Generating %d constructor fields and %d setter fields",
+ builderDef.getConstructorFieldsForBuilder().size(),
+ builderDef.getSetterFieldsForBuilder().size());
+
// Generate backing fields for each DTO field (constructor and setter fields)
for (FieldDto fieldDto : builderDef.getConstructorFieldsForBuilder()) {
FieldSpec fieldSpec = createFieldMember(fieldDto);
@@ -110,10 +123,14 @@ public void generateBuilder(BuilderDefinitionDto builderDef) throws BuilderExcep
// Generate field-specific functions in Builder (constructor fields first, then setter fields)
for (FieldDto fieldDto : builderDef.getConstructorFieldsForBuilder()) {
List methodSpecs = createFieldMethods(fieldDto, builderTypeName);
+ logger.debug(
+ " Generated %d methods for field: %s", methodSpecs.size(), fieldDto.getFieldName());
classBuilder.addMethods(methodSpecs);
}
for (FieldDto fieldDto : builderDef.getSetterFieldsForBuilder()) {
List methodSpecs = createFieldMethods(fieldDto, builderTypeName);
+ logger.debug(
+ " Generated %d methods for field: %s", methodSpecs.size(), fieldDto.getFieldName());
classBuilder.addMethods(methodSpecs);
}
@@ -133,7 +150,13 @@ public void generateBuilder(BuilderDefinitionDto builderDef) throws BuilderExcep
classBuilder.addAnnotation(createAnnotationGenerated());
classBuilder.addAnnotation(createAnnotationBuilderImplementation(dtoBaseClass));
+ logger.debug(
+ "Writing builder class to file: %s.%s",
+ builderDef.getBuilderTypeName().getPackageName(),
+ builderDef.getBuilderTypeName().getClassName());
writeClassToFile(builderDef.getBuilderTypeName().getPackageName(), classBuilder.build());
+ logger.debug(
+ "Successfully generated builder: %s", builderDef.getBuilderTypeName().getClassName());
}
private void writeClassToFile(String packageName, TypeSpec typeSpec) throws BuilderException {
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java
index 7e6259b5..c198ec2f 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java
@@ -61,6 +61,22 @@ public static boolean isNoMethodOfObjectClass(ExecutableElement mth) {
|| Strings.CS.equals("Object", simpleNameOfParent));
}
+ /**
+ * Gets all methods of a class, including inherited methods, excluding methods from {@code
+ * java.lang.Object}.
+ *
+ * @param context the processing context
+ * @param typeElement the type element to get methods from
+ * @return filtered list excluding Object class methods
+ */
+ public static List findAllPossibleSettersOfClass(
+ TypeElement typeElement, ProcessingContext context) {
+ return ElementFilter.methodsIn(context.getAllMembers(typeElement)).stream()
+ .filter(JavaLangAnalyser::isNoMethodOfObjectClass)
+ .filter(JavaLangAnalyser::isSetterForField)
+ .toList();
+ }
+
/**
* Helper function to filter methods with specific annotations.
*
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingContext.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingContext.java
index 6fe18222..7a348d07 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingContext.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingContext.java
@@ -122,40 +122,61 @@ public boolean isSameType(TypeMirror type1, TypeMirror type2) {
}
/**
- * Reports an error at the location of the given element.
+ * Logs an info-level message that appears in normal Maven output.
*
- * @param element the element where the error occurred
- * @param message the error message
+ * @param message the info message to log
*/
- public void error(Element element, String message) {
- logger.error(element, message);
+ public void info(String message) {
+ logger.info(message);
}
/**
- * Reports an error message.
+ * Logs an info-level message with a formatted string.
*
- * @param message the error message
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
*/
- public void error(String message) {
- logger.error(message);
+ public void info(String format, Object... args) {
+ logger.info(format, args);
}
/**
- * Logs an informational message.
+ * Logs a debug message visible when Maven is run with -X flag.
*
- * @param message the message to log
+ * @param message the debug message to log
*/
- public void log(String message) {
- logger.log(message);
+ public void debug(String message) {
+ logger.debug(message);
}
/**
- * Reports a warning at the location of the given element with the specified message.
+ * Logs a debug message with a formatted string.
+ *
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
+ */
+ public void debug(String format, Object... args) {
+ logger.debug(format, args);
+ }
+
+ /**
+ * Reports a warning at the location of the given element with a formatted message.
*
* @param element the element where the warning occurred, used for location information
- * @param message the warning message to be reported
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
+ */
+ public void warning(Element element, String format, Object... args) {
+ logger.warning(element, format, args);
+ }
+
+ /**
+ * Reports an error with a formatted message.
+ *
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
*/
- public void warning(Element element, String message) {
- logger.warning(element, message);
+ public void error(String format, Object... args) {
+ logger.error(format, args);
}
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingLogger.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingLogger.java
index 65d8c417..613d3243 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingLogger.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/ProcessingLogger.java
@@ -25,6 +25,7 @@
package org.javahelpers.simple.builders.processor.util;
import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic;
@@ -37,55 +38,85 @@ public class ProcessingLogger {
/** Util class for exposing messages of type {@code javax.annotation.processing.Messager}. */
private final Messager messager;
+ /** Flag indicating if debug logging is enabled. */
+ private final boolean debugEnabled;
+
/**
- * Constructs a new ProcessingLogger with the specified Messager instance. The Messager is used to
- * report errors, warnings, and other notices during annotation processing.
+ * Constructs a new ProcessingLogger with the specified ProcessingEnvironment. The Messager is
+ * used to report errors, warnings, and other notices during annotation processing. Debug logging
+ * is enabled by setting the compiler argument: -Averbose=true
*
- * @param messager the Messager instance to use for reporting messages, must not be null
+ * @param processingEnv the processing environment providing messager and options
*/
- public ProcessingLogger(Messager messager) {
- this.messager = messager;
+ public ProcessingLogger(ProcessingEnvironment processingEnv) {
+ this.messager = processingEnv.getMessager();
+ this.debugEnabled = "true".equalsIgnoreCase(processingEnv.getOptions().get("verbose"));
}
/**
- * Reports an error at the location of the given element with the specified message. The error
- * will be reported to the underlying Messager instance.
+ * Reports an error with a formatted message.
*
- * @param e the element where the error occurred, used for location information
- * @param message the error message to be reported
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
*/
- public void error(Element e, String message) {
- messager.printMessage(Diagnostic.Kind.ERROR, message, e);
+ public void error(String format, Object... args) {
+ messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args));
}
/**
- * Reports an error with the specified message. The error will be reported to the underlying
- * Messager instance.
+ * Posts an info-level message (NOTE level in Maven). Used for important status information about
+ * builder generation.
*
- * @param message the error message to be reported
+ * @param message the info message to be posted
*/
- public void error(String message) {
- messager.printMessage(Diagnostic.Kind.ERROR, message);
+ public void info(String message) {
+ messager.printMessage(Diagnostic.Kind.NOTE, message);
}
/**
- * Posts an informational note message to the underlying Messager. This is typically used for
- * non-critical information during processing.
+ * Posts an info-level message with a formatted string.
*
- * @param message the informational message to be posted
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
*/
- public void log(String message) {
- messager.printMessage(Diagnostic.Kind.NOTE, message);
+ public void info(String format, Object... args) {
+ messager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args));
+ }
+
+ /**
+ * Posts a debug message with OTHER level. Used for detailed tracing of the builder generation
+ * process. Only visible when enabled via -Averbose=true compiler argument.
+ *
+ * @param message the debug message to be posted
+ */
+ public void debug(String message) {
+ if (debugEnabled) {
+ String formatWithDebug = "[DEBUG] " + message;
+ messager.printMessage(Diagnostic.Kind.OTHER, formatWithDebug);
+ }
+ }
+
+ /**
+ * Posts a debug message with a formatted string. Only visible when enabled via -Averbose=true.
+ *
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
+ */
+ public void debug(String format, Object... args) {
+ if (debugEnabled) {
+ String formatWithDebug = "[DEBUG] " + format;
+ messager.printMessage(Diagnostic.Kind.OTHER, String.format(formatWithDebug, args));
+ }
}
/**
- * Reports a warning at the location of the given element with the specified message. The warning
- * will be reported to the underlying Messager instance.
+ * Reports a warning at the location of the given element with a formatted message.
*
* @param e the element where the warning occurred, used for location information
- * @param message the warning message to be reported
+ * @param format the format string
+ * @param args arguments referenced by the format specifiers in the format string
*/
- public void warning(Element e, String message) {
- messager.printMessage(Diagnostic.Kind.WARNING, message, e);
+ public void warning(Element e, String format, Object... args) {
+ messager.printMessage(Diagnostic.Kind.WARNING, String.format(format, args), e);
}
}
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java
index 836776af..ec4673b2 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java
@@ -18,6 +18,65 @@
/** Tests for the {@link BuilderProcessor} class. */
class BuilderProcessorTest {
+ @Test
+ void shouldGenerateBasicBuilder() {
+ // Given: A simple class with one field
+ String className = "SimpleDto";
+ String builderClassName = className + "Builder";
+
+ JavaFileObject sourceFile =
+ ProcessorTestUtils.simpleBuilderClass(
+ "test",
+ className,
+ """
+ private String value;
+ public String getValue() { return value; }
+ public void setValue(String value) { this.value = value; }
+ """);
+
+ // When
+ Compilation compilation = compile(sourceFile);
+
+ // Then: Builder should be generated successfully
+ String generatedCode = loadGeneratedSource(compilation, builderClassName);
+ assertGenerationSucceeded(compilation, builderClassName, generatedCode);
+ ProcessorAsserts.assertContaining(
+ generatedCode, "public SimpleDtoBuilder value(String value)", "public SimpleDto build()");
+ }
+
+ @Test
+ void shouldLogDebugMessagesWhenVerboseModeEnabled() {
+ // Given
+ JavaFileObject sourceFile =
+ ProcessorTestUtils.simpleBuilderClass(
+ "test",
+ "VerboseTest",
+ """
+ private String name;
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
+ """);
+
+ // When: Compile with verbose=true to enable debug logging
+ Compilation compilation =
+ javac()
+ .withProcessors(new BuilderProcessor())
+ .withOptions("-Averbose=true")
+ .compile(sourceFile);
+
+ // Then: Compilation succeeds and debug messages are present
+ assertThat(compilation).succeeded();
+
+ // Verify key debug messages are logged
+ ProcessorAsserts.assertHadNoteContaining(
+ compilation,
+ "simple-builders: PROCESSING ROUND START",
+ "[DEBUG] simple-builders: Processing element: VerboseTest",
+ "[DEBUG] Extracting builder definition from: test.VerboseTest",
+ "[DEBUG] -> Adding field: name",
+ "[DEBUG] Successfully generated builder: VerboseTestBuilder");
+ }
+
@Test
void shouldFailWhenAnnotationPlacedOnInterface() {
// Given: @SimpleBuilder used on an interface instead of a class
@@ -33,10 +92,10 @@ public interface WrongTargetInterface { void x(); }
// When
Compilation compilation = compile(source);
- // Then: compilation should fail with appropriate error message
- assertThat(compilation).failed();
+ // Then: compilation should succeed but with a warning
+ assertThat(compilation).succeeded();
assertThat(compilation)
- .hadErrorContaining("The SimpleBuilder should be annotated on a class or record");
+ .hadWarningContaining("The SimpleBuilder should be annotated on a class or record");
}
@Test
@@ -54,9 +113,9 @@ public abstract class AbstractAnnotated { }
// When
Compilation compilation = compile(source);
- // Then: compilation should fail with appropriate error message
- assertThat(compilation).failed();
- assertThat(compilation).hadErrorContaining("The SimpleBuilder should not be abstract");
+ // Then: compilation should succeed but with a warning
+ assertThat(compilation).succeeded();
+ assertThat(compilation).hadWarningContaining("The SimpleBuilder should not be abstract");
}
@Test
@@ -138,7 +197,9 @@ void shouldHandleFieldsOfConstructorAndSetters() {
generatedCode,
"public CtorAndSetterBuilder()", // empty constructor for builder
"public CtorAndSetterBuilder name(String name)", // helpermethod for setter
+ "public CtorAndSetterBuilder name(Supplier nameSupplier)", // supplier for setter
"public CtorAndSetterBuilder a(int a)", // helpermethod for constructor param
+ "public CtorAndSetterBuilder a(Supplier aSupplier)", // supplier for constructor
"CtorAndSetter result = new CtorAndSetter(this.a.value());",
"this.name.ifChanged(result::setName);");
}
@@ -291,6 +352,110 @@ public void notAConstructor(String name) {
generatedCode, "public WrongMethodAnnotationBuilder name(String name)");
}
+ @Test
+ void shouldGenerateSupplierMethodsForConstructorFields() {
+ // Given
+ String packageName = "test";
+ String className = "RequiredFieldViaConstructor";
+ String builderClassName = className + "Builder";
+
+ JavaFileObject sourceFile =
+ ProcessorTestUtils.simpleBuilderClass(
+ packageName,
+ className,
+ """
+ private final String name;
+ private final int age;
+ private String description;
+
+ public RequiredFieldViaConstructor(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() { return name; }
+ public int getAge() { return age; }
+ public String getDescription() { return description; }
+ public void setDescription(String description) { this.description = description; }
+ """);
+
+ // When
+ Compilation compilation = compile(sourceFile);
+
+ // Then
+ String generatedCode = loadGeneratedSource(compilation, builderClassName);
+ assertGenerationSucceeded(compilation, builderClassName, generatedCode);
+
+ // Constructor fields should have both basic setter AND supplier methods
+ ProcessorAsserts.assertContaining(
+ generatedCode,
+ // Basic setters for constructor fields
+ "public RequiredFieldViaConstructorBuilder name(String name)",
+ "public RequiredFieldViaConstructorBuilder age(int age)",
+ // Supplier methods for constructor fields (the bug we fixed!)
+ "public RequiredFieldViaConstructorBuilder name(Supplier nameSupplier)",
+ "public RequiredFieldViaConstructorBuilder age(Supplier ageSupplier)",
+ // Setter field should also have supplier
+ "public RequiredFieldViaConstructorBuilder description(String description)",
+ "public RequiredFieldViaConstructorBuilder description(Supplier descriptionSupplier)");
+ }
+
+ @Test
+ void shouldGenerateConsumerMethodsForConstructorFieldsWithCustomTypes() {
+ // Given
+ String packageName = "test";
+ String className = "RequiredCustomTypeViaConstructor";
+ String builderClassName = className + "Builder";
+
+ JavaFileObject dto =
+ ProcessorTestUtils.simpleBuilderClass(
+ packageName,
+ className,
+ """
+ private final Address address;
+ private String notes;
+
+ public RequiredCustomTypeViaConstructor(Address address) {
+ this.address = address;
+ }
+
+ public Address getAddress() { return address; }
+ public String getNotes() { return notes; }
+ public void setNotes(String notes) { this.notes = notes; }
+ """);
+
+ JavaFileObject helper =
+ ProcessorTestUtils.forSource(
+ """
+ package test;
+ public class Address {
+ private String street;
+ public Address() {}
+ public String getStreet() { return street; }
+ public void setStreet(String street) { this.street = street; }
+ }
+ """);
+
+ // When
+ Compilation compilation = compile(dto, helper);
+
+ // Then
+ String generatedCode = loadGeneratedSource(compilation, builderClassName);
+ assertGenerationSucceeded(compilation, builderClassName, generatedCode);
+
+ // Constructor field with custom type should have setter, supplier AND consumer methods
+ ProcessorAsserts.assertContaining(
+ generatedCode,
+ // Basic setter for constructor field
+ "public RequiredCustomTypeViaConstructorBuilder address(Address address)",
+ // Supplier method for constructor field
+ "public RequiredCustomTypeViaConstructorBuilder address(Supplier addressSupplier)",
+ // Consumer method for constructor field (the key coverage addition!)
+ "public RequiredCustomTypeViaConstructorBuilder address(Consumer addressConsumer)",
+ "Address consumer = new Address();",
+ "addressConsumer.accept(consumer);");
+ }
+
@Test
void shouldHandleRecordWithoutGeneratingBuilder() {
// Given
@@ -1237,6 +1402,7 @@ public void setOk(int ok) {}
// should be filtered
public void nonSetterMethods() {}
public void settingChanged(String nonSetterValue) {}
+ public int setSetterWithReturnValue(int returnValue) { return returnValue; }
private void setHidden(int hidden) {}
public static void setUtil(int util) {}
public void setRisky(int risk) throws Exception {}
@@ -1257,6 +1423,7 @@ public void setRisky(int risk) throws Exception {}
contains("this.ok = changedValue(ok);"),
notContains("nonSetterMethods"),
notContains("settingChanged"),
+ notContains("setterWithReturnValue"),
notContains("hidden"),
notContains("util(int util)"),
notContains("risky(int risk)"),
@@ -2183,10 +2350,10 @@ public void setFieldWithOwnGeneric(F fieldWithOwnGeneri
assertThat(compilation).succeeded();
assertThat(compilation)
.hadWarningContaining(
- "Field fieldWithOwnGeneric has field-specific generics, so it will be ignored");
+ "Field 'fieldWithOwnGeneric' has field-specific generics, so it will be ignored");
assertThat(compilation)
.hadWarningContaining(
- "Field fieldWithExtendedClassGeneric has field-specific generics, so it will be ignored");
+ "Field 'fieldWithExtendedClassGeneric' has field-specific generics, so it will be ignored");
ProcessorAsserts.assertingResult(
generatedCode,
// name field should be present
@@ -2223,10 +2390,10 @@ public class InnerClass {
// When
Compilation compilation = compile(source);
- // Then: compilation should fail with appropriate error message
- assertThat(compilation).failed();
+ // Then: compilation should succeed but with a warning
+ assertThat(compilation).succeeded();
assertThat(compilation)
- .hadErrorContaining("The SimpleBuilder should be declared on a top-level class only");
+ .hadWarningContaining("The SimpleBuilder should be declared on a top-level class only");
}
@Test
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java
index 8d721199..ec219347 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java
@@ -109,4 +109,17 @@ public static void assertContaining(String generatedCode, String... searches) {
.toArray(AssertRecord[]::new);
assertingResult(generatedCode, checks);
}
+
+ /**
+ * Asserts that the compilation contains all the specified note messages. Useful for verifying
+ * debug or info logging output.
+ *
+ * @param compilation the compilation result
+ * @param noteMessages the note messages to check for
+ */
+ public static void assertHadNoteContaining(Compilation compilation, String... noteMessages) {
+ for (String noteMessage : noteMessages) {
+ assertThat(compilation).hadNoteContaining(noteMessage);
+ }
+ }
}