From 6ca1f00d38dda0c6472933726d6d6ffa871afd74 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 19:01:35 +0200 Subject: [PATCH 01/16] Adding debugging capabilities --- .../processor/util/ProcessingContext.java | 18 +++++++++++++++ .../processor/util/ProcessingLogger.java | 22 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) 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..b16adc17 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 @@ -149,6 +149,24 @@ public void log(String message) { logger.log(message); } + /** + * Logs an info-level message that appears in normal Maven output. + * + * @param message the info message to log + */ + public void info(String message) { + logger.info(message); + } + + /** + * Logs a debug message visible when Maven is run with -X flag. + * + * @param message the debug message to log + */ + public void debug(String message) { + logger.debug(message); + } + /** * Reports a warning at the location of the given element with the specified message. * 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..61547252 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 @@ -70,7 +70,7 @@ public void error(String message) { /** * Posts an informational note message to the underlying Messager. This is typically used for - * non-critical information during processing. + * non-critical information during processing. Maps to Maven's NOTE level. * * @param message the informational message to be posted */ @@ -78,6 +78,26 @@ public void log(String message) { messager.printMessage(Diagnostic.Kind.NOTE, message); } + /** + * Posts an info-level message (NOTE level in Maven). Used for important status information about + * builder generation. + * + * @param message the info message to be posted + */ + public void info(String message) { + messager.printMessage(Diagnostic.Kind.NOTE, message); + } + + /** + * Posts a debug message with NOTE level. Used for detailed tracing of the builder generation + * process. Visible when Maven is run with -X flag. + * + * @param message the debug message to be posted + */ + public void debug(String message) { + messager.printMessage(Diagnostic.Kind.NOTE, message); + } + /** * Reports a warning at the location of the given element with the specified message. The warning * will be reported to the underlying Messager instance. From 1aca4f8940bf022d82c5c97dc2e2d3c462043f04 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 20:57:58 +0200 Subject: [PATCH 02/16] Extending the functionality of logger --- DEBUG_LOGGING.md | 104 ++++++++++++++++++ example/pom.xml | 6 + .../processor/util/ProcessingContext.java | 62 +++++++++++ .../processor/util/ProcessingLogger.java | 88 +++++++++++++-- 4 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 DEBUG_LOGGING.md diff --git a/DEBUG_LOGGING.md b/DEBUG_LOGGING.md new file mode 100644 index 00000000..33c1c6d7 --- /dev/null +++ b/DEBUG_LOGGING.md @@ -0,0 +1,104 @@ +# Debug Logging for Simple Builders + +The Simple Builders annotation processor supports conditional debug logging that provides detailed information about the builder generation process. + +## 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 + + + ${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 + + + true + + + + + +``` + +## Example Debug Output + +When debug logging is enabled, you'll see detailed output like: + +``` +[INFO] simple-builders: Processing round started. Found 3 annotated elements. +[INFO] simple-builders: Processing element: PersonDto +[INFO] Extracting builder definition from: org.example.PersonDto +[INFO] Builder will be generated as: org.example.PersonDtoBuilder +[INFO] Found 17 total methods in class hierarchy +[INFO] Analyzing method: setName with 1 parameter(s) +[INFO] -> Adding field: name (type: java.lang.String) +[INFO] Analyzing method: setAge with 1 parameter(s) +[INFO] -> Adding field: age (type: int) +[INFO] Found 2 relevant setter methods (filtered from 17 total methods) +[INFO] Processed 2 setters: added 2 fields, skipped 0 duplicates +[INFO] Starting code generation for builder: PersonDtoBuilder +[INFO] Generating methods for 2 setter fields +[INFO] Generated 2 method(s) for field: name +[INFO] Generated 2 method(s) for field: age +[INFO] Writing builder class to file: org.example.PersonDtoBuilder +[INFO] Successfully generated builder: PersonDtoBuilder +[INFO] simple-builders: Successfully generated builder for: PersonDto +``` + +**Note**: Debug messages 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/example/pom.xml b/example/pom.xml index 1aba9198..ed78686e 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} + + ${simplebuilder.verbose} + 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 b16adc17..01d920f3 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 @@ -131,6 +131,17 @@ public void error(Element element, String message) { logger.error(element, message); } + /** + * Reports an error at the location of the given element with a formatted message. + * + * @param element the element where the error occurred + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void error(Element element, String format, Object... args) { + logger.error(element, format, args); + } + /** * Reports an error message. * @@ -140,6 +151,16 @@ public void error(String message) { logger.error(message); } + /** + * 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 error(String format, Object... args) { + logger.error(format, args); + } + /** * Logs an informational message. * @@ -149,6 +170,16 @@ public void log(String message) { logger.log(message); } + /** + * Logs an informational message with a formatted string. + * + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void log(String format, Object... args) { + logger.log(format, args); + } + /** * Logs an info-level message that appears in normal Maven output. * @@ -158,6 +189,16 @@ public void info(String message) { logger.info(message); } + /** + * Logs an info-level message with a formatted string. + * + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void info(String format, Object... args) { + logger.info(format, args); + } + /** * Logs a debug message visible when Maven is run with -X flag. * @@ -167,6 +208,16 @@ public void debug(String message) { logger.debug(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 the specified message. * @@ -176,4 +227,15 @@ public void debug(String message) { public void warning(Element element, String message) { logger.warning(element, message); } + + /** + * 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 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); + } } 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 61547252..eaea2266 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,14 +38,19 @@ 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")); } /** @@ -58,6 +64,17 @@ public void error(Element e, String message) { messager.printMessage(Diagnostic.Kind.ERROR, message, e); } + /** + * Reports an error at the location of the given element with a formatted message. + * + * @param e the element where the error occurred, used for location information + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void error(Element e, String format, Object... args) { + messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args), e); + } + /** * Reports an error with the specified message. The error will be reported to the underlying * Messager instance. @@ -68,6 +85,16 @@ public void error(String message) { messager.printMessage(Diagnostic.Kind.ERROR, message); } + /** + * 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 error(String format, Object... args) { + messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args)); + } + /** * Posts an informational note message to the underlying Messager. This is typically used for * non-critical information during processing. Maps to Maven's NOTE level. @@ -78,6 +105,16 @@ public void log(String message) { messager.printMessage(Diagnostic.Kind.NOTE, message); } + /** + * Posts an informational note message with a formatted string. + * + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void log(String format, Object... args) { + messager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args)); + } + /** * Posts an info-level message (NOTE level in Maven). Used for important status information about * builder generation. @@ -89,13 +126,37 @@ public void info(String message) { } /** - * Posts a debug message with NOTE level. Used for detailed tracing of the builder generation - * process. Visible when Maven is run with -X flag. + * Posts an info-level message with a formatted string. + * + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + 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) { - messager.printMessage(Diagnostic.Kind.NOTE, message); + if (debugEnabled) { + messager.printMessage(Diagnostic.Kind.OTHER, message); + } + } + + /** + * 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) { + messager.printMessage(Diagnostic.Kind.OTHER, String.format(format, args)); + } } /** @@ -108,4 +169,15 @@ public void debug(String message) { public void warning(Element e, String message) { messager.printMessage(Diagnostic.Kind.WARNING, message, e); } + + /** + * 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 format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void warning(Element e, String format, Object... args) { + messager.printMessage(Diagnostic.Kind.WARNING, String.format(format, args), e); + } } From eca24f24df247e7fba38b7ed60a99292ceecada6 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:04:49 +0200 Subject: [PATCH 03/16] Extending logging functionality --- DEBUG_LOGGING.md | 10 ++- .../processor/util/ProcessingContext.java | 90 +++++++------------ .../processor/util/ProcessingLogger.java | 80 ++++++----------- 3 files changed, 66 insertions(+), 114 deletions(-) diff --git a/DEBUG_LOGGING.md b/DEBUG_LOGGING.md index 33c1c6d7..048bd06e 100644 --- a/DEBUG_LOGGING.md +++ b/DEBUG_LOGGING.md @@ -2,6 +2,11 @@ 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/failure messages for each builder generated +- **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`. @@ -59,9 +64,12 @@ Set it permanently in your `pom.xml`: ## Example Debug Output -When debug logging is enabled, you'll see detailed output like: +When debug logging is enabled, you'll see detailed output with visual separators: ``` +[INFO] ======================================== +[INFO] simple-builders: PROCESSING ROUND START +[INFO] ======================================== [INFO] simple-builders: Processing round started. Found 3 annotated elements. [INFO] simple-builders: Processing element: PersonDto [INFO] Extracting builder definition from: org.example.PersonDto 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 01d920f3..85ab32a3 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 @@ -121,65 +121,6 @@ public boolean isSameType(TypeMirror type1, TypeMirror type2) { return typeUtils.isSameType(type1, type2); } - /** - * Reports an error at the location of the given element. - * - * @param element the element where the error occurred - * @param message the error message - */ - public void error(Element element, String message) { - logger.error(element, message); - } - - /** - * Reports an error at the location of the given element with a formatted message. - * - * @param element the element where the error occurred - * @param format the format string - * @param args arguments referenced by the format specifiers in the format string - */ - public void error(Element element, String format, Object... args) { - logger.error(element, format, args); - } - - /** - * Reports an error message. - * - * @param message the error message - */ - public void error(String message) { - logger.error(message); - } - - /** - * 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 error(String format, Object... args) { - logger.error(format, args); - } - - /** - * Logs an informational message. - * - * @param message the message to log - */ - public void log(String message) { - logger.log(message); - } - - /** - * Logs an informational message with a formatted string. - * - * @param format the format string - * @param args arguments referenced by the format specifiers in the format string - */ - public void log(String format, Object... args) { - logger.log(format, args); - } - /** * Logs an info-level message that appears in normal Maven output. * @@ -238,4 +179,35 @@ public void warning(Element element, String message) { 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 error(String format, Object... args) { + logger.error(format, args); + } + + /** + * Reports an error at the location of the given element. + * + * @param element the element where the error occurred + * @param message the error message + */ + public void error(Element element, String message) { + logger.error(element, message); + } + + /** + * Reports an error at the location of the given element with a formatted message. + * + * @param element the element where the error occurred + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void error(Element element, String format, Object... args) { + logger.error(element, 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 eaea2266..5702020d 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 @@ -53,38 +53,6 @@ public ProcessingLogger(ProcessingEnvironment processingEnv) { 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. - * - * @param e the element where the error occurred, used for location information - * @param message the error message to be reported - */ - public void error(Element e, String message) { - messager.printMessage(Diagnostic.Kind.ERROR, message, e); - } - - /** - * Reports an error at the location of the given element with a formatted message. - * - * @param e the element where the error occurred, used for location information - * @param format the format string - * @param args arguments referenced by the format specifiers in the format string - */ - public void error(Element e, String format, Object... args) { - messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args), e); - } - - /** - * Reports an error with the specified message. The error will be reported to the underlying - * Messager instance. - * - * @param message the error message to be reported - */ - public void error(String message) { - messager.printMessage(Diagnostic.Kind.ERROR, message); - } - /** * Reports an error with a formatted message. * @@ -95,26 +63,6 @@ public void error(String format, Object... args) { messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args)); } - /** - * Posts an informational note message to the underlying Messager. This is typically used for - * non-critical information during processing. Maps to Maven's NOTE level. - * - * @param message the informational message to be posted - */ - public void log(String message) { - messager.printMessage(Diagnostic.Kind.NOTE, message); - } - - /** - * Posts an informational note message with a formatted string. - * - * @param format the format string - * @param args arguments referenced by the format specifiers in the format string - */ - public void log(String format, Object... args) { - messager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args)); - } - /** * Posts an info-level message (NOTE level in Maven). Used for important status information about * builder generation. @@ -143,7 +91,8 @@ public void info(String format, Object... args) { */ public void debug(String message) { if (debugEnabled) { - messager.printMessage(Diagnostic.Kind.OTHER, message); + String formatWithDebug = "[DEBUG] " + message; + messager.printMessage(Diagnostic.Kind.OTHER, formatWithDebug); } } @@ -155,7 +104,8 @@ public void debug(String message) { */ public void debug(String format, Object... args) { if (debugEnabled) { - messager.printMessage(Diagnostic.Kind.OTHER, String.format(format, args)); + String formatWithDebug = "[DEBUG] " + format; + messager.printMessage(Diagnostic.Kind.OTHER, String.format(formatWithDebug, args)); } } @@ -180,4 +130,26 @@ public void warning(Element e, String message) { public void warning(Element e, String format, Object... args) { messager.printMessage(Diagnostic.Kind.WARNING, String.format(format, args), e); } + + /** + * Reports an error at the location of the given element with the specified message. The error + * will be reported to the underlying Messager instance. + * + * @param e the element where the error occurred, used for location information + * @param message the error message to be reported + */ + public void error(Element e, String message) { + messager.printMessage(Diagnostic.Kind.ERROR, message, e); + } + + /** + * Reports an error at the location of the given element with a formatted message. + * + * @param e the element where the error occurred, used for location information + * @param format the format string + * @param args arguments referenced by the format specifiers in the format string + */ + public void error(Element e, String format, Object... args) { + messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args), e); + } } From 951ab1d4467b8d8407f6cc646e12bf7ff20cd11c Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:08:15 +0200 Subject: [PATCH 04/16] Extending debugging --- .../builders/processor/BuilderProcessor.java | 33 +++++--- .../util/BuilderDefinitionCreator.java | 76 ++++++++++++++----- .../processor/util/JavaCodeGenerator.java | 27 ++++++- .../processor/util/JavaLangAnalyser.java | 16 ++++ .../processor/BuilderProcessorTest.java | 4 +- 5 files changed, 125 insertions(+), 31 deletions(-) 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..39b8f6b1 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 @@ -57,20 +57,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 +86,29 @@ public boolean process(Set 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 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()); + context.error( + annotatedElement, "simple-builders: Failed to generate builder - %s", ex.getMessage()); } } return true; 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..0917f79b 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 @@ -39,7 +39,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; @@ -84,12 +83,17 @@ public static BuilderDefinitionDto extractFromElement( validateAnnotatedElement(annotatedElement); TypeElement annotatedType = (TypeElement) annotatedElement; + context.debug("Extracting builder definition from: %s", annotatedType.getQualifiedName()); + 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); @@ -104,40 +108,75 @@ public static BuilderDefinitionDto extractFromElement( } } - List allMembers = context.getAllMembers(annotatedType); - List methods = ElementFilter.methodsIn(allMembers); - // Build a set of constructor field names to avoid duplicates from setters Set ctorFieldNames = result.getConstructorFieldsForBuilder().stream() .map(FieldDto::getFieldName) .collect(toSet()); + // Build fields on base of setters + List methods = findAllPossibleSettersOfClass(annotatedType, context); + int processedCount = 0; + int addedCount = 0; + int skippedCount = 0; for (ExecutableElement mth : methods) { - // nur public - if (isMethodRelevantForBuilder(mth)) { + context.debug( + "Analyzing method: %s with %d parameter(s)", + mth.getSimpleName(), mth.getParameters().size()); + if (isMethodRelevantForBuilder(mth, context)) { // Avoid adding setter-derived fields that duplicate constructor params Optional maybeField = createFieldFromSetter(mth, context); if (maybeField.isPresent()) { + processedCount++; FieldDto field = maybeField.get(); if (!ctorFieldNames.contains(field.getFieldName())) { + addedCount++; + 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); result.addField(field); + continue; } + } else { + context.debug(" -> Skipping method (already in constructor): %s", mth.getSimpleName()); } } + skippedCount++; } + context.debug( + "Processed %d possible setters: added %d fields, skipped %d", + processedCount, addedCount, skippedCount); + return result; } - private static boolean isMethodRelevantForBuilder(ExecutableElement mth) { - return isSetterForField(mth) - && isNoMethodOfObjectClass(mth) - && hasNoThrowablesDeclared(mth) - && hasNoReturnValue(mth) - && hasNotAnnotation(IgnoreInBuilder.class, mth) - && isNotPrivate(mth) - && isNotStatic(mth); + 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( @@ -246,7 +285,9 @@ private static Optional createFieldFromSetter( // Should never happen, just to be sure here context.warning( mth.getEnclosingElement(), - "Setter method " + methodName + " has " + parameters.size() + " parameters, expected 1"); + "Method %s has %d parameters, expected 1", + mth.getSimpleName(), + mth.getParameters().size()); return Optional.empty(); } VariableElement fieldParameter = parameters.get(0); @@ -265,7 +306,7 @@ private static Optional createFieldFromSetter( // extracting type of field MethodParameterDto fieldParameterDto = map2MethodParameter(fieldParameter, context); if (fieldParameterDto == null) { - context.warning(mth.getEnclosingElement(), "Could not extract type of field " + fieldName); + context.warning(mth.getEnclosingElement(), "Could not extract type of field '%s'", fieldName); return Optional.empty(); } TypeName fieldType = fieldParameterDto.getParameterType(); @@ -284,7 +325,8 @@ 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(); } 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/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java index 836776af..45ec4615 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 @@ -2183,10 +2183,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 From 46c476cfa881ec19f6dbb9422cfa940921ec5ea5 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:08:31 +0200 Subject: [PATCH 05/16] Adding an example of ignored fields --- .../simple/builders/example/MannschaftDto.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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; } From 43806d00a901646ceaf3beacabfc48d0ea905164 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:16:18 +0200 Subject: [PATCH 06/16] Fixing compiler warnings and updating documentation --- DEBUG_LOGGING.md | 52 +++++++++---------- README.md | 7 +++ example/pom.xml | 6 +-- .../builders/processor/BuilderProcessor.java | 2 + 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/DEBUG_LOGGING.md b/DEBUG_LOGGING.md index 048bd06e..0edbf924 100644 --- a/DEBUG_LOGGING.md +++ b/DEBUG_LOGGING.md @@ -27,9 +27,9 @@ Add a property in your `pom.xml`: maven-compiler-plugin 3.13.0 - - ${simplebuilder.verbose} - + + -Averbose=${simplebuilder.verbose} + @@ -53,9 +53,9 @@ Set it permanently in your `pom.xml`: maven-compiler-plugin 3.13.0 - - true - + + -Averbose=true + @@ -67,30 +67,30 @@ Set it permanently in your `pom.xml`: When debug logging is enabled, you'll see detailed output with visual separators: ``` -[INFO] ======================================== +[INFO] [DEBUG] =============================== [INFO] simple-builders: PROCESSING ROUND START -[INFO] ======================================== -[INFO] simple-builders: Processing round started. Found 3 annotated elements. -[INFO] simple-builders: Processing element: PersonDto -[INFO] Extracting builder definition from: org.example.PersonDto -[INFO] Builder will be generated as: org.example.PersonDtoBuilder -[INFO] Found 17 total methods in class hierarchy -[INFO] Analyzing method: setName with 1 parameter(s) -[INFO] -> Adding field: name (type: java.lang.String) -[INFO] Analyzing method: setAge with 1 parameter(s) -[INFO] -> Adding field: age (type: int) -[INFO] Found 2 relevant setter methods (filtered from 17 total methods) -[INFO] Processed 2 setters: added 2 fields, skipped 0 duplicates -[INFO] Starting code generation for builder: PersonDtoBuilder -[INFO] Generating methods for 2 setter fields -[INFO] Generated 2 method(s) for field: name -[INFO] Generated 2 method(s) for field: age -[INFO] Writing builder class to file: org.example.PersonDtoBuilder -[INFO] Successfully generated builder: PersonDtoBuilder +[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 use `Diagnostic.Kind.OTHER` which appears as `[INFO]` in Maven output. +**Note**: Debug messages are prefixed with `[DEBUG]` and use `Diagnostic.Kind.OTHER` which appears as `[INFO]` in Maven output. ## Normal Output (Without Debug) diff --git a/README.md b/README.md index 6b74fc7e..b42cf5e0 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - [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) @@ -169,6 +170,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: diff --git a/example/pom.xml b/example/pom.xml index ed78686e..e7c618db 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -80,9 +80,9 @@ ${project.version} - - ${simplebuilder.verbose} - + + -Averbose=${simplebuilder.verbose} + 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 39b8f6b1..1706b6d0 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; From 8398111f8bb7f460eb1325d94eda6b61ab282afd Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:16:38 +0200 Subject: [PATCH 07/16] Fixing links in documentation an duplicated licensing chapter --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index b42cf5e0..cc5823a8 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - [Building from Source](#building-from-source) - [Contributing](#contributing) - [License](#license) +- [Links](#links) ## What is Simple Builders? @@ -217,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. From 077a608768ce0a093a46c01245d41ad32d77ccf8 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:33:22 +0200 Subject: [PATCH 08/16] Changing example to have constructors too --- .../javahelpers/simple/builders/example/PersonDto.java | 10 +++++----- .../simple/builders/example/goal/PersonDtoBuilder.java | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) 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; } From ce98d05aa9f414c89ec20e20b45e883e31c11f88 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:34:41 +0200 Subject: [PATCH 09/16] Refactoring BuilderDefinitionCreator to reduce complexity Moving logic in subfunctions --- .../processor/dtos/BuilderDefinitionDto.java | 10 +++ .../util/BuilderDefinitionCreator.java | 73 ++++++++++++++++--- 2 files changed, 71 insertions(+), 12 deletions(-) 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 0917f79b..8df96e51 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; @@ -85,6 +86,20 @@ public static BuilderDefinitionDto extractFromElement( 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(); @@ -97,16 +112,45 @@ public static BuilderDefinitionDto extractFromElement( // 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; + } + + /** + * 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 = @@ -114,30 +158,25 @@ public static BuilderDefinitionDto extractFromElement( .map(FieldDto::getFieldName) .collect(toSet()); - // Build fields on base of setters List methods = findAllPossibleSettersOfClass(annotatedType, context); int processedCount = 0; int addedCount = 0; int skippedCount = 0; + for (ExecutableElement mth : methods) { context.debug( "Analyzing method: %s with %d parameter(s)", mth.getSimpleName(), mth.getParameters().size()); + if (isMethodRelevantForBuilder(mth, context)) { - // Avoid adding setter-derived fields that duplicate constructor params Optional maybeField = createFieldFromSetter(mth, context); if (maybeField.isPresent()) { processedCount++; FieldDto field = maybeField.get(); if (!ctorFieldNames.contains(field.getFieldName())) { addedCount++; - 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); - result.addField(field); + logFieldAddition(field, context); + setterFields.add(field); continue; } } else { @@ -151,7 +190,17 @@ public static BuilderDefinitionDto extractFromElement( "Processed %d possible setters: added %d fields, skipped %d", processedCount, addedCount, skippedCount); - return result; + return setterFields; + } + + /** 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( From 0200a2a44b2c16592c840efba5115a9f56bdd30b Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:52:17 +0200 Subject: [PATCH 10/16] Adding test for helper-functions on constructor fields --- .../processor/BuilderProcessorTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) 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 45ec4615..52827cd8 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 @@ -138,7 +138,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 +293,54 @@ 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 shouldHandleRecordWithoutGeneratingBuilder() { // Given From 88d2d67633b70c9e9712d6c869f75ad847ad3939 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 22:53:34 +0200 Subject: [PATCH 11/16] Refactoring field-generation so that constructor and setter fields are generated via same base-logic --- .../util/BuilderDefinitionCreator.java | 105 ++++++++++-------- 1 file changed, 57 insertions(+), 48 deletions(-) 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 8df96e51..c044020e 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 @@ -327,8 +327,6 @@ 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 parameters = mth.getParameters(); if (parameters.size() != 1) { // Should never happen, just to be sure here @@ -339,33 +337,6 @@ private static Optional createFieldFromSetter( mth.getParameters().size()); 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 '%s'", 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., @@ -379,16 +350,17 @@ private static Optional createFieldFromSetter( 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); } /** @@ -397,23 +369,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( From ca9637aad7cd377c972af5a869fdf0b9e45ea749 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 23:07:04 +0200 Subject: [PATCH 12/16] Changing handling of exceptions to not stop processing next elements --- DEBUG_LOGGING.md | 4 +++- .../builders/processor/BuilderProcessor.java | 3 ++- .../util/BuilderDefinitionCreator.java | 8 ++++---- .../processor/BuilderProcessorTest.java | 18 +++++++++--------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/DEBUG_LOGGING.md b/DEBUG_LOGGING.md index 0edbf924..2c3fe3c4 100644 --- a/DEBUG_LOGGING.md +++ b/DEBUG_LOGGING.md @@ -4,7 +4,9 @@ The Simple Builders annotation processor supports conditional debug logging that ## Logging Levels -- **INFO**: Always visible - Shows success/failure messages for each builder generated +- **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 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 1706b6d0..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 @@ -109,7 +109,8 @@ public boolean process(Set annotations, RoundEnvironment "simple-builders: Successfully generated builder for: %s", annotatedElement.getSimpleName()); } catch (BuilderException ex) { - context.error( + // 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()); } } 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 c044020e..53e24de4 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 @@ -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( 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 52827cd8..803c8b71 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 @@ -33,10 +33,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 +54,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 @@ -2273,10 +2273,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 From f17c485ac6cc8e55de9059016d54cb3033be918f Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 23:43:38 +0200 Subject: [PATCH 13/16] Refactoring tests to increase coverage --- .../processor/BuilderProcessorTest.java | 115 ++++++++++++++++++ .../processor/testing/ProcessorAsserts.java | 13 ++ 2 files changed, 128 insertions(+) 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 803c8b71..3617a50b 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 @@ -341,6 +400,62 @@ public RequiredFieldViaConstructor(String name, int age) { "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 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); + } + } } From 0aa02e07f1e52fe2564d7e5adc769445c707bb58 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 23:43:55 +0200 Subject: [PATCH 14/16] Removing unused functions from ProcessingContext and ProcessingLogging --- .../processor/util/ProcessingContext.java | 31 ----------------- .../processor/util/ProcessingLogger.java | 33 ------------------- 2 files changed, 64 deletions(-) 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 85ab32a3..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 @@ -159,16 +159,6 @@ public void debug(String format, Object... args) { logger.debug(format, args); } - /** - * Reports a warning at the location of the given element with the specified message. - * - * @param element the element where the warning occurred, used for location information - * @param message the warning message to be reported - */ - public void warning(Element element, String message) { - logger.warning(element, message); - } - /** * Reports a warning at the location of the given element with a formatted message. * @@ -189,25 +179,4 @@ public void warning(Element element, String format, Object... args) { public void error(String format, Object... args) { logger.error(format, args); } - - /** - * Reports an error at the location of the given element. - * - * @param element the element where the error occurred - * @param message the error message - */ - public void error(Element element, String message) { - logger.error(element, message); - } - - /** - * Reports an error at the location of the given element with a formatted message. - * - * @param element the element where the error occurred - * @param format the format string - * @param args arguments referenced by the format specifiers in the format string - */ - public void error(Element element, String format, Object... args) { - logger.error(element, 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 5702020d..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 @@ -109,17 +109,6 @@ public void debug(String format, Object... 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. - * - * @param e the element where the warning occurred, used for location information - * @param message the warning message to be reported - */ - public void warning(Element e, String message) { - messager.printMessage(Diagnostic.Kind.WARNING, message, e); - } - /** * Reports a warning at the location of the given element with a formatted message. * @@ -130,26 +119,4 @@ public void warning(Element e, String message) { public void warning(Element e, String format, Object... args) { messager.printMessage(Diagnostic.Kind.WARNING, String.format(format, args), e); } - - /** - * Reports an error at the location of the given element with the specified message. The error - * will be reported to the underlying Messager instance. - * - * @param e the element where the error occurred, used for location information - * @param message the error message to be reported - */ - public void error(Element e, String message) { - messager.printMessage(Diagnostic.Kind.ERROR, message, e); - } - - /** - * Reports an error at the location of the given element with a formatted message. - * - * @param e the element where the error occurred, used for location information - * @param format the format string - * @param args arguments referenced by the format specifiers in the format string - */ - public void error(Element e, String format, Object... args) { - messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args), e); - } } From c6f63d2d48b901a1cd0f091dfc0e7af71e1f402c Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 23:54:39 +0200 Subject: [PATCH 15/16] Adding test for setter with return-value --- .../simple/builders/processor/BuilderProcessorTest.java | 2 ++ 1 file changed, 2 insertions(+) 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 3617a50b..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 @@ -1402,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 {} @@ -1422,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)"), From d91563b34950dac0705529dcdff539ccb50630d9 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 12 Oct 2025 23:55:01 +0200 Subject: [PATCH 16/16] Refactoring edgecase, which should never happen to decrease coverage footprint --- .../builders/processor/util/BuilderDefinitionCreator.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 53e24de4..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 @@ -330,11 +330,7 @@ private static Optional createFieldFromSetter( List parameters = mth.getParameters(); if (parameters.size() != 1) { // Should never happen, just to be sure here - context.warning( - mth.getEnclosingElement(), - "Method %s has %d parameters, expected 1", - mth.getSimpleName(), - mth.getParameters().size()); + context.warning(mth, "Unexpected state of method."); return Optional.empty(); }