name) {
+ this.lastName = name;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName.orElse(null);
+ }
+}
diff --git a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/FieldConflictExampleBuilder.java b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/FieldConflictExampleBuilder.java
new file mode 100644
index 00000000..7a1defe7
--- /dev/null
+++ b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/FieldConflictExampleBuilder.java
@@ -0,0 +1,239 @@
+package org.javahelpers.simple.builders.example;
+
+import static org.javahelpers.simple.builders.core.util.TrackedValue.changedValue;
+import static org.javahelpers.simple.builders.core.util.TrackedValue.initialValue;
+import static org.javahelpers.simple.builders.core.util.TrackedValue.unsetValue;
+
+import java.util.Optional;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import javax.annotation.processing.Generated;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.javahelpers.simple.builders.core.annotations.BuilderImplementation;
+import org.javahelpers.simple.builders.core.interfaces.IBuilderBase;
+import org.javahelpers.simple.builders.core.util.BuilderToStringStyle;
+import org.javahelpers.simple.builders.core.util.TrackedValue;
+
+/**
+ * Builder for {@code org.javahelpers.simple.builders.example.FieldConflictExample}.
+ *
+ * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.FieldConflictExample with
+ * method chaining and validation. Use the static {@code create()} method
+ * to obtain a new builder instance, configure the desired properties using
+ * the setter methods, and then call {@code build()} to create the final DTO.
+ */
+@Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
+@BuilderImplementation(
+ forClass = FieldConflictExample.class
+)
+public class FieldConflictExampleBuilder implements IBuilderBase {
+ /**
+ * Tracked value for name: name.
+ */
+ private TrackedValue name = unsetValue();
+
+ /**
+ * Tracked value for nameOptional: name.
+ */
+ private TrackedValue> nameOptional = unsetValue();
+
+ /**
+ * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.FieldConflictExample}.
+ */
+ public FieldConflictExampleBuilder() {
+ }
+
+ /**
+ * Initialisation of builder for {@code org.javahelpers.simple.builders.example.FieldConflictExample} by a instance.
+ *
+ * @param instance object instance for initialisiation
+ */
+ public FieldConflictExampleBuilder(FieldConflictExample instance) {
+ }
+
+ /**
+ * Creating a new builder for {@code org.javahelpers.simple.builders.example.FieldConflictExample}.
+ *
+ * @return builder for {@code org.javahelpers.simple.builders.example.FieldConflictExample}
+ */
+ public static FieldConflictExampleBuilder create() {
+ return new FieldConflictExampleBuilder();
+ }
+
+ /**
+ * Sets the value for name.
+ *
+ * @param name name
+ * @return current instance of builder
+ */
+ public FieldConflictExampleBuilder name(String name) {
+ this.name = changedValue(name);
+ return this;
+ }
+
+ /**
+ * Sets the value for name.
+ *
+ * @param name name
+ * @return current instance of builder
+ */
+ public FieldConflictExampleBuilder name(Optional name) {
+ this.nameOptional = changedValue(name);
+ return this;
+ }
+
+ /**
+ * Sets the value for name by executing the provided consumer.
+ *
+ * @param nameStringBuilderConsumer consumer providing an instance of name
+ * @return current instance of builder
+ */
+ public FieldConflictExampleBuilder name(Consumer nameStringBuilderConsumer) {
+ StringBuilder builder = new StringBuilder();
+ nameStringBuilderConsumer.accept(builder);
+ this.name = changedValue(builder.toString());
+ return this;
+ }
+
+ /**
+ * Sets the value for name by invoking the provided supplier.
+ *
+ * @param nameSupplier supplier for name
+ * @return current instance of builder
+ */
+ public FieldConflictExampleBuilder name(Supplier nameSupplier) {
+ this.name = changedValue(nameSupplier.get());
+ return this;
+ }
+
+ /**
+ * Sets the String value for name by using String.format(format, args).
+ * See {@link String#format(String, Object...)} for details.
+ *
+ * @param format A format string
+ * @param args Arguments referenced by the format specifiers in the format string.
+ * @return current instance of builder
+ */
+ public FieldConflictExampleBuilder name(String format, Object... args) {
+ this.nameOptional = changedValue(Optional.of(String.format(format, args)));
+ return this;
+ }
+
+ /**
+ * Sets the value for nameOptional by executing the provided consumer.
+ *
+ * @param nameOptionalStringBuilderConsumer consumer providing an instance of name
+ * @return current instance of builder
+ */
+ public FieldConflictExampleBuilder nameOptional(
+ Consumer nameOptionalStringBuilderConsumer) {
+ StringBuilder builder = new StringBuilder();
+ nameOptionalStringBuilderConsumer.accept(builder);
+ this.nameOptional = changedValue(Optional.of(builder.toString()));
+ return this;
+ }
+
+ /**
+ * Validates that the name field is not null or empty.
+ *
+ * @return this builder instance for chaining
+ * @throws IllegalArgumentException if name is null or empty
+ */
+ FieldConflictExampleBuilder validateName() {
+ if (!name.isSet() || name.value().trim().isEmpty()) {
+ throw new IllegalArgumentException("Name cannot be null or empty");
+ }
+ return this;
+ }
+
+ /**
+ * Conditionally applies builder modifications if the condition is true.
+ *
+ * @param condition the condition to evaluate
+ * @param yesCondition the consumer to apply if condition is true
+ * @return this builder instance
+ */
+ public FieldConflictExampleBuilder conditional(BooleanSupplier condition,
+ Consumer yesCondition) {
+ return conditional(condition, yesCondition, null);
+ }
+
+ /**
+ * Conditionally applies builder modifications based on a condition evaluation.
+ *
+ * @param condition the condition to evaluate
+ * @param trueCase the consumer to apply if condition is true
+ * @param falseCase the consumer to apply if condition is false (can be null)
+ * @return this builder instance
+ */
+ public FieldConflictExampleBuilder conditional(BooleanSupplier condition,
+ Consumer trueCase,
+ Consumer falseCase) {
+ if (condition.getAsBoolean()) {
+ trueCase.accept(this);
+ } else if (falseCase != null) {
+ falseCase.accept(this);
+ }
+ return this;
+ }
+
+ /**
+ * Builds the configured DTO instance.
+ */
+ @Override
+ public FieldConflictExample build() {
+ FieldConflictExample result = new FieldConflictExample();
+ this.name.ifSet(result::setName);
+ this.nameOptional.ifSet(result::setName);
+ return result;
+ }
+
+ /**
+ * Returns a string representation of this builder, including only fields that have been set.
+ *
+ * @return string representation of the builder
+ */
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE)
+ .append("name", this.name)
+ .append("nameOptional", this.nameOptional)
+ .toString();
+ }
+
+ /**
+ * Interface that can be implemented by the DTO to provide fluent modification methods.
+ */
+ public interface With {
+ /**
+ * Initializes a builder from an instance of this class, using methods of this builder to change values and returns the new built object.
+ *
+ * @param b the consumer to apply modifications
+ * @return the modified instance
+ */
+ default FieldConflictExample with(Consumer b) {
+ FieldConflictExampleBuilder builder;
+ try {
+ builder = new FieldConflictExampleBuilder(FieldConflictExample.class.cast(this));
+ } catch (ClassCastException ex) {
+ throw new IllegalArgumentException("The interface 'FieldConflictExampleBuilder.With' should only be implemented by classes, which could be casted to 'FieldConflictExample'", ex);
+ }
+ b.accept(builder);
+ return builder.build();
+ }
+
+ /**
+ * Creates a builder initialized from this instance.
+ *
+ * @return a builder initialized with this instance's values
+ */
+ default FieldConflictExampleBuilder with() {
+ try {
+ return new FieldConflictExampleBuilder(FieldConflictExample.class.cast(this));
+ } catch (ClassCastException ex) {
+ throw new IllegalArgumentException("The interface 'FieldConflictExampleBuilder.With' should only be implemented by classes, which could be casted to 'FieldConflictExample'", ex);
+ }
+ }
+ }
+}
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 dbfc3939..1b111fa7 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
@@ -70,6 +70,7 @@ public class BuilderProcessor extends AbstractProcessor {
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
ProcessingLogger logger = new ProcessingLogger(processingEnv);
+ logger.debug("Starting BuilderProcessor...");
// Read global configuration from compiler arguments
CompilerArgumentsReader reader = new CompilerArgumentsReader(processingEnv);
@@ -80,6 +81,14 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
this.codeGenerator = new JavaCodeGenerator(processingEnv, logger);
this.jacksonModuleGenerator = new JacksonModuleGenerator(processingEnv, logger);
+ // Initialize GeneratorRegistry once during processor initialization
+ context.debugStartOperation("Initializing generator registry");
+ try {
+ context.getGeneratorRegistry();
+ } finally {
+ context.debugEndOperation();
+ }
+
SourceVersion current = processingEnv.getSourceVersion();
this.supportedJdk = isAtLeastJava17(current);
if (!this.supportedJdk) {
@@ -102,6 +111,8 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
for (var module : modules) {
codeGenerator.generateJacksonModule(module);
}
+ // Reset indentation after Jackson module generation as well
+ context.resetIndentation();
return false;
}
@@ -126,9 +137,7 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
elementsToProcess.addAll(roundEnv.getElementsAnnotatedWith(annotation));
}
- context.debug("===============================");
context.info("simple-builders: PROCESSING ROUND START");
- context.debug("===============================");
context.debug(
"simple-builders: Processing round started. Found %d annotated elements.",
elementsToProcess.size());
@@ -139,24 +148,35 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
.sorted(Comparator.comparing(element -> element.getSimpleName().toString()))
.toList();
+ int successfulGenerations = 0;
for (Element annotatedElement : sortedElements) {
+ context.debugStartOperation("Processing element: " + annotatedElement.getSimpleName());
try {
- context.debug("------------------------------------");
- context.debug("simple-builders: Processing element: %s", annotatedElement.getSimpleName());
- context.debug("------------------------------------");
// Resolve configuration per-element to handle all layers
// (defaults, global, template, inline)
BuilderConfiguration config = reader.resolveConfiguration(annotatedElement);
+ context.debug("Configuration resolved: %s", config);
process(annotatedElement, config);
- context.info(
- "simple-builders: Successfully generated builder for: %s",
- annotatedElement.getSimpleName());
+ successfulGenerations++;
} catch (BuilderException ex) {
- // All builder generation failures are warnings to allow other builders to be generated
+ // 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());
+ } finally {
+ context.debugEndOperation();
}
}
+
+ // Log summary of builder generation
+ if (successfulGenerations > 0) {
+ context.info(
+ "simple-builders: Successfully generated %d builder(s) in this processing round",
+ successfulGenerations);
+ }
+
+ // Reset indentation level at the end of each processing round to prevent cascading errors
+ context.resetIndentation();
return true;
}
@@ -183,6 +203,14 @@ private void process(Element annotatedElement, BuilderConfiguration config)
// Collect info for Jackson Module if enabled
jacksonModuleGenerator.addEntry(builderDef, annotatedElement);
+ context.debug("Jackson module entry added");
+
+ // Add summary of what was generated
+ context.debugEndOperation(
+ "Generated builder with %d fields and %d methods for %s",
+ builderDef.getAllFieldsForBuilder().size(),
+ builderDef.getCoreMethods().size(),
+ builderDef.getBuilderTypeName().getClassName());
}
/**
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java
index 76bd40ea..e47fab5a 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java
@@ -93,9 +93,6 @@ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext co
CodeBlock javadoc = createClassJavadoc(dtoClass);
builderDto.setClassJavadoc(javadoc.toString());
-
- context.debug(
- "Added class JavaDoc to builder %s", builderDto.getBuilderTypeName().getClassName());
}
/**
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java
index 5e6a33d4..394f784e 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java
@@ -107,9 +107,6 @@ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext co
// Add toString() method
MethodDto toStringMethod = createToStringMethod(builderDto);
builderDto.addCoreMethod(toStringMethod);
-
- context.debug(
- "Added core methods to builder %s", builderDto.getBuilderTypeName().getClassName());
}
/** Creates the build() method. */
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java
index 7fa470db..865fc7ae 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java
@@ -81,10 +81,6 @@ public boolean appliesTo(
public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
AnnotationDto generatedAnnotation = createGeneratedAnnotation();
builderDto.addClassAnnotation(generatedAnnotation);
-
- context.debug(
- "Added @Generated annotation to builder %s",
- builderDto.getBuilderTypeName().getClassName());
}
/**
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratorRegistry.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratorRegistry.java
index eed36cad..b3d46955 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratorRegistry.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratorRegistry.java
@@ -74,7 +74,8 @@ public GeneratorRegistry(ProcessingContext context, ProcessingEnvironment proces
loadAllGenerators();
sortGeneratorsByPriority();
- context.debug(
+ // Log the closing message with └─ without changing indentation level
+ context.debugEndOperation(
"Initialized GeneratorRegistry with %d method generators and %d builder enhancers",
methodGenerators.size(), builderEnhancers.size());
}
@@ -91,11 +92,12 @@ public List generateAllMethods(
FieldDto field, TypeName dtoType, TypeName builderType) {
List allMethods = new ArrayList<>();
+ context.debugStartOperation("Processing method generators");
for (MethodGenerator generator : methodGenerators) {
if (generator.appliesTo(field, dtoType, context)) {
try {
context.debug(
- " -> Applying method generator: %s (priority: %d)",
+ "Applying: %s (priority: %d)",
generator.getClass().getSimpleName(), generator.getPriority());
List generatedMethods = generator.generateMethods(field, builderType, context);
@@ -110,6 +112,7 @@ public List generateAllMethods(
}
}
}
+ context.debugEndOperation("Generated %d methods", allMethods.size());
return allMethods;
}
@@ -121,18 +124,17 @@ public List generateAllMethods(
* @param dtoType the DTO type the builder is for
*/
public void enhanceBuilder(BuilderDefinitionDto builderDto, TypeName dtoType) {
+ int appliedEnhancers = 0;
+ context.debugStartOperation("Processing class based enhancer");
for (BuilderEnhancer enhancer : builderEnhancers) {
if (enhancer.appliesTo(builderDto, dtoType, context)) {
try {
context.debug(
- " -> Applying builder enhancer: %s (priority: %d)",
+ "Applying: %s (priority: %d)",
enhancer.getClass().getSimpleName(), enhancer.getPriority());
enhancer.enhanceBuilder(builderDto, context);
-
- context.debug(
- " Enhanced builder %s with %s",
- builderDto.getBuilderTypeName().getClassName(), enhancer.getClass().getSimpleName());
+ appliedEnhancers++;
} catch (Exception e) {
context.error(
"Failed to apply enhancer %s to builder %s: %s",
@@ -142,19 +144,23 @@ public void enhanceBuilder(BuilderDefinitionDto builderDto, TypeName dtoType) {
}
}
}
+
+ if (appliedEnhancers > 0) {
+ context.debugEndOperation("Applied %d builder enhancers", appliedEnhancers);
+ } else {
+ context.debugEndOperation();
+ }
}
/**
- * Loads all generators (built-in and custom) via ServiceLoader.
+ * Loads all available generators from the service loader and separates them into method
+ * generators and builder enhancers.
*
* Generators are discovered by looking for implementations of {@link Generator} declared in
* {@code META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator} files.
*
*
The loaded generators are separated into method generators and builder enhancers based on
- * their type (using the sealed interface hierarchy).
- *
- *
If loading fails for any generator, a warning is logged but processing continues with the
- * remaining generators.
+ * their type.
*/
private void loadAllGenerators() {
int methodGenCount = 0;
@@ -178,21 +184,16 @@ private void loadAllGenerators() {
if (generator instanceof MethodGenerator methodGen) {
methodGenerators.add(methodGen);
methodGenCount++;
- context.debug(
- "Loaded method generator: %s (priority: %d)",
- generatorClassName, methodGen.getPriority());
} else if (generator instanceof BuilderEnhancer enhancer) {
builderEnhancers.add(enhancer);
enhancerCount++;
- context.debug(
- "Loaded builder enhancer: %s (priority: %d)",
- generatorClassName, enhancer.getPriority());
}
}
} catch (Exception e) {
context.error("Failed to load generators: %s", e.getMessage());
}
+ // Only log summary, not individual generators (too verbose)
context.debug(
"Loaded %d method generators and %d builder enhancers total",
methodGenCount, enhancerCount);
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java
index 09dfb12d..aa51495b 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java
@@ -92,10 +92,6 @@ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext co
// Add the IBuilderBase interface to the builder
InterfaceName builderBaseInterface = createBuilderBaseInterface(builderDto);
builderDto.addInterface(builderBaseInterface);
-
- context.debug(
- "Added IBuilderBase interface to builder %s",
- builderDto.getBuilderTypeName().getClassName());
}
/**
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java
index d3ef7ad2..9e058abe 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java
@@ -92,10 +92,6 @@ public boolean appliesTo(
public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
AnnotationDto jacksonAnnotation = createJsonPOJOBuilderAnnotation(builderDto);
builderDto.addClassAnnotation(jacksonAnnotation);
-
- context.debug(
- "Added @JsonPOJOBuilder annotation to builder %s",
- builderDto.getBuilderTypeName().getClassName());
}
/**
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java
index ce3cedab..1440336e 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java
@@ -96,25 +96,17 @@ public boolean appliesTo(
@Override
public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
- NestedTypeDto withInterface = createWithInterface(builderDto, context);
+ NestedTypeDto withInterface = createWithInterface(builderDto);
builderDto.addNestedType(withInterface);
-
- context.debug(
- "Added With interface to builder %s", builderDto.getBuilderTypeName().getClassName());
}
/**
* Creates the "With" interface for the builder.
*
* @param builderDto the builder definition
- * @param context the processing context
* @return the nested type DTO for the With interface
*/
- private NestedTypeDto createWithInterface(
- BuilderDefinitionDto builderDto, ProcessingContext context) {
- context.debug(
- "Creating With interface for: %s", builderDto.getBuilderTypeName().getClassName());
-
+ private NestedTypeDto createWithInterface(BuilderDefinitionDto builderDto) {
NestedTypeDto withInterface = new NestedTypeDto();
withInterface.setTypeName("With");
withInterface.setKind(NestedTypeDto.NestedTypeKind.INTERFACE);
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderConfigurationReader.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderConfigurationReader.java
index 4d5cbe1a..2e41d05f 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderConfigurationReader.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderConfigurationReader.java
@@ -231,8 +231,7 @@ public BuilderConfiguration readFromTemplate(Element element) {
// If @SimpleBuilder is present, ignore template annotations
if (hasSimpleBuilderAnnotation(element)) {
logger.debug(
- "Template annotations ignored for '%s' (direct @SimpleBuilder present)",
- element.getSimpleName());
+ "Template annotations ignored because @SimpleBuilder present", element.getSimpleName());
return null;
}
@@ -240,6 +239,7 @@ public BuilderConfiguration readFromTemplate(Element element) {
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
BuilderConfiguration templateConfig = checkForTemplateAnnotation(mirror, element);
if (templateConfig != null) {
+ logger.debug("Annotation based Configuration: %s", templateConfig.toString());
return templateConfig;
}
}
@@ -350,7 +350,8 @@ private BuilderConfiguration extractOptionsFromTemplateMirror(AnnotationMirror t
* @return the fully resolved configuration with all sources merged
*/
public BuilderConfiguration resolveConfiguration(Element element) throws BuilderException {
- logger.debug("Resolving configuration for element: %s", element.getSimpleName());
+ String elementName = element.getSimpleName().toString();
+ logger.debugStartOperation("Resolving configuration for element: %s", elementName);
BuilderConfiguration templateConfig = readFromTemplate(element);
BuilderConfiguration inlineConfig = readFromInlineOptions(element);
@@ -364,8 +365,7 @@ public BuilderConfiguration resolveConfiguration(Element element) throws Builder
// Validate access modifiers and warn about problematic configurations
validateAccessModifiers(element, result);
- logger.debug("Configuration resolved for '%s': %s", element.getSimpleName(), result.toString());
-
+ logger.debugEndOperation("Resulting configuration resolved: %s", result.toString());
return result;
}
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 1f3dc538..5123ac6f 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
@@ -64,7 +64,8 @@ public static BuilderDefinitionDto extractFromElement(
validateAnnotatedElement(annotatedElement);
TypeElement annotatedType = (TypeElement) annotatedElement;
- context.debug("Extracting builder definition from: %s", annotatedType.getQualifiedName());
+ context.debugStartOperation(
+ "Extracting builder definition from: %s", annotatedType.getQualifiedName());
BuilderDefinitionDto result = initializeBuilderDefinition(annotatedType, context);
@@ -82,6 +83,11 @@ public static BuilderDefinitionDto extractFromElement(
// Apply builder enhancers (including With interface generation)
context.getGeneratorRegistry().enhanceBuilder(result, result.getBuildingTargetTypeName());
+ context.debug("Builder will be generated as: %s", result.getBuilderTypeName().getClassName());
+
+ context.debugEndOperation(
+ "Builder definition extracted: %s", result.getBuilderTypeName().getClassName());
+
return result;
}
@@ -123,14 +129,15 @@ private static List extractConstructorFields(
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());
+ context.debugStartOperation(
+ "Analyzing constructor with %d parameter(s)", ctor.getParameters().size());
+
TypeName builderType =
MethodGeneratorUtil.createGenericTypeName(
builderDef.getBuilderTypeName(), builderDef.getGenerics());
for (VariableElement param : ctor.getParameters()) {
+ context.debugStartOperation("Analyzing parameter: %s", param.getSimpleName());
Optional fieldFromCtor =
createFieldFromConstructor(
annotatedType, param, builderType, context, fieldNameRegistry);
@@ -140,6 +147,8 @@ private static List extractConstructorFields(
constructorFields.add(field);
}
}
+
+ context.debugEndOperation();
}
return constructorFields;
}
@@ -155,6 +164,7 @@ private static List extractSetterFields(
BuilderDefinitionDto result,
ProcessingContext context,
Map fieldNameRegistry) {
+ context.debugStartOperation("Analysing setters for finding fields");
List setterFields = new LinkedList<>();
// Build a set of constructor field names to avoid duplicates from setters
@@ -173,7 +183,7 @@ private static List extractSetterFields(
result.getBuilderTypeName(), result.getGenerics());
for (ExecutableElement mth : methods) {
- context.debug(
+ context.debugStartOperation(
"Analyzing method: %s with %d parameter(s)",
mth.getSimpleName(), mth.getParameters().size());
@@ -207,9 +217,13 @@ private static List extractSetterFields(
}
}
- context.debug(
- "Processed %d possible setters: added %d fields, skipped %d",
- processedCount, addedCount, skippedCount);
+ if (addedCount != 0 || skippedCount != 0) {
+ context.debugEndOperation(
+ "Processed %d possible setters: added %d fields, skipped %d",
+ processedCount, addedCount, skippedCount);
+ } else {
+ context.debugEndOperation("No setters found");
+ }
return setterFields;
}
@@ -221,29 +235,29 @@ private static void logFieldAddition(FieldDto field, ProcessingContext context)
&& !field.getFieldType().getPackageName().isEmpty()) {
fieldTypeName = field.getFieldType().getPackageName() + "." + fieldTypeName;
}
- context.debug(" -> Adding field: %s (type: %s)", field.getFieldName(), fieldTypeName);
+ context.debugEndOperation("Adding field: %s (type: %s)", field.getFieldName(), fieldTypeName);
}
private static boolean isMethodRelevantForBuilder(
ExecutableElement mth, ProcessingContext context) {
if (!hasNoThrowablesDeclared(mth)) {
- context.debug(" -> Skipping: declares throwables");
+ context.debug("Skipping: declares throwables");
return false;
}
if (!hasNoReturnValue(mth)) {
- context.debug(" -> Skipping: has return value");
+ context.debug("Skipping: has return value");
return false;
}
if (!hasNotAnnotation(IgnoreInBuilder.class, mth)) {
- context.debug(" -> Skipping: has @IgnoreInBuilder annotation");
+ context.debug("Skipping: has @IgnoreInBuilder annotation");
return false;
}
if (!isNotPrivate(mth)) {
- context.debug(" -> Skipping: is private");
+ context.debug("Skipping: is private");
return false;
}
if (!isNotStatic(mth)) {
- context.debug(" -> Skipping: is static");
+ context.debug("Skipping: is static");
return false;
}
return true;
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/FieldAnnotationExtractor.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/FieldAnnotationExtractor.java
index 777cd2c7..013151aa 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/FieldAnnotationExtractor.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/FieldAnnotationExtractor.java
@@ -80,10 +80,6 @@ public static List extractAnnotations(
List annotations = new ArrayList<>();
List extends AnnotationMirror> annotationMirrors = param.getAnnotationMirrors();
- context.debug(
- " -> Extracting %d annotation(s) from field %s",
- annotationMirrors.size(), param.getSimpleName());
-
for (AnnotationMirror mirror : annotationMirrors) {
extractAnnotation(mirror, context).ifPresent(annotations::add);
}
@@ -104,10 +100,6 @@ public static List extractAnnotations(
List annotations = new ArrayList<>();
List extends AnnotationMirror> annotationMirrors = typeMirror.getAnnotationMirrors();
- context.debug(
- " -> Extracting %d annotation(s) from type %s",
- annotationMirrors.size(), typeMirror.toString());
-
for (AnnotationMirror mirror : annotationMirrors) {
extractAnnotation(mirror, context).ifPresent(annotations::add);
}
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 e452c436..75cfbf5a 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
@@ -75,9 +75,8 @@ public JavaCodeGenerator(ProcessingEnvironment processingEnv, ProcessingLogger l
* @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());
-
+ logger.debugStartOperation(
+ "Code generation for builder: %s", builderDef.getBuilderTypeName().getClassName());
TypeSpec.Builder classBuilder = createClassBuilder(builderDef);
addClassMetadata(classBuilder, builderDef);
addFieldsToBuilder(classBuilder, builderDef);
@@ -86,12 +85,8 @@ public void generateBuilder(BuilderDefinitionDto builderDef) throws BuilderExcep
addNestedTypesToBuilder(classBuilder, builderDef);
addAnnotationsToBuilder(classBuilder, builderDef);
- logger.debug(
- "Writing builder class to file: %s.%s",
- builderDef.getBuilderTypeName().getPackageName(),
- builderDef.getBuilderTypeName().getClassName());
writeBuilderClassToFile(classBuilder.build(), builderDef);
- logger.debug(
+ logger.debugEndOperation(
"Successfully generated builder: %s", builderDef.getBuilderTypeName().getClassName());
}
@@ -101,8 +96,11 @@ private TypeSpec.Builder createClassBuilder(BuilderDefinitionDto builderDef) {
logger.debug("Builder has %d generic type parameter(s)", builderDef.getGenerics().size());
}
- return TypeSpec.classBuilder(builderBaseClass)
- .addTypeVariables(map2TypeVariables(builderDef.getGenerics()));
+ TypeSpec.Builder result =
+ TypeSpec.classBuilder(builderBaseClass)
+ .addTypeVariables(map2TypeVariables(builderDef.getGenerics()));
+ logger.debug("Class builder created");
+ return result;
}
private void addClassMetadata(TypeSpec.Builder classBuilder, BuilderDefinitionDto builderDef) {
@@ -123,10 +121,12 @@ private void addClassMetadata(TypeSpec.Builder classBuilder, BuilderDefinitionDt
JavapoetMapper.mapInterfaceToTypeName(interfaceName);
classBuilder.addSuperinterface(interfaceType);
}
+
+ logger.debug("Class metadata added");
}
private void addFieldsToBuilder(TypeSpec.Builder classBuilder, BuilderDefinitionDto builderDef) {
- logger.debug(
+ logger.debugStartOperation(
"Generating %d constructor fields and %d setter fields",
builderDef.getConstructorFieldsForBuilder().size(),
builderDef.getSetterFieldsForBuilder().size());
@@ -141,22 +141,26 @@ private void addFieldsToBuilder(TypeSpec.Builder classBuilder, BuilderDefinition
FieldSpec fieldSpec = createFieldMember(fieldDto);
classBuilder.addField(fieldSpec);
}
+ logger.debugEndOperation("Fields added: %d fields", builderDef.getAllFieldsForBuilder().size());
}
private void addMethodsToBuilder(TypeSpec.Builder classBuilder, BuilderDefinitionDto builderDef) {
// Collect all methods from all fields, setting javadoc and tracking field relationship
Map allMethods = collectAllMethods(builderDef);
+ logger.debugStartOperation("Adding Methods for %d candidates", allMethods.size());
// Resolve conflicts and sort by ordering
List resolvedMethods = resolveMethodConflicts(allMethods);
- logger.debug(" Resolved %d methods after conflict resolution", resolvedMethods.size());
+ logger.debug("Resolved %d methods after conflict resolution", resolvedMethods.size());
// Generate all methods in order
+ int generatedCnt = 0;
for (MethodDto methodDto : resolvedMethods) {
- logger.debug(" Generating method: %s", methodDto);
MethodSpec methodSpec = createMethod(methodDto);
classBuilder.addMethod(methodSpec);
+ generatedCnt++;
}
+ logger.debugEndOperation("%d Methods added", generatedCnt);
}
private Map collectAllMethods(BuilderDefinitionDto builderDef) {
@@ -184,29 +188,45 @@ private Map collectAllMethods(BuilderDefinitionDto builderD
private void addConstructorsToBuilder(
TypeSpec.Builder classBuilder, BuilderDefinitionDto builderDef) {
generateConstructors(classBuilder, builderDef);
+ logger.debug("Constructors added");
}
private void addNestedTypesToBuilder(
TypeSpec.Builder classBuilder, BuilderDefinitionDto builderDef) {
+ if (CollectionUtils.isEmpty(builderDef.getNestedTypes())) {
+ return;
+ }
// Adding nested types (e.g., With interface)
+ logger.debugStartOperation("Generating %d nested type(s)", builderDef.getNestedTypes().size());
for (NestedTypeDto nestedType : builderDef.getNestedTypes()) {
TypeSpec nestedTypeSpec = createNestedType(nestedType);
classBuilder.addType(nestedTypeSpec);
- logger.debug(" Generated nested type: %s", nestedType.getTypeName());
+ logger.debug("Generated nested type: %s", nestedType.getTypeName());
}
+ logger.debugEndOperation("Nested types added");
}
private void addAnnotationsToBuilder(
TypeSpec.Builder classBuilder, BuilderDefinitionDto builderDef) {
+ if (CollectionUtils.isEmpty(builderDef.getClassAnnotations())) {
+ return;
+ }
// Adding annotations from enhancers
for (AnnotationDto annotation : builderDef.getClassAnnotations()) {
AnnotationSpec annotationSpec = map2AnnotationSpec(annotation);
classBuilder.addAnnotation(annotationSpec);
}
+
+ logger.debug("Class-level annotations added");
}
private void writeBuilderClassToFile(TypeSpec typeSpec, BuilderDefinitionDto builderDef)
throws BuilderException {
+ logger.debug(
+ "Writing builder class to file: %s.%s",
+ builderDef.getBuilderTypeName().getPackageName(),
+ builderDef.getBuilderTypeName().getClassName());
+
// Extract qualified name from the builder definition
String qualifiedName = builderDef.getBuilderTypeName().getFullQualifiedName();
@@ -296,10 +316,20 @@ private void writeSimpleClassToFile(String packageName, TypeSpec typeSpec)
* @return list of all methods with conflicts resolved, sorted by ordering for proper generation
*/
private List resolveMethodConflicts(Map methodToField) {
- Map signatureToMethod = new HashMap<>();
+ MethodDto.MethodComparator comparator = new MethodDto.MethodComparator();
+
+ // Sort entries using MethodComparator for deterministic conflict resolution
+ // This ensures consistent behavior when multiple methods have the same signature
+ List> sortedEntries =
+ methodToField.entrySet().stream()
+ .sorted((e1, e2) -> comparator.compare(e1.getKey(), e2.getKey()))
+ .toList();
+
+ // Use LinkedHashMap to preserve insertion order from sorted entries
+ Map signatureToMethod = new java.util.LinkedHashMap<>();
// Process all methods and resolve conflicts
- for (Map.Entry entry : methodToField.entrySet()) {
+ for (Map.Entry entry : sortedEntries) {
MethodDto method = entry.getKey();
FieldDto field = entry.getValue();
String signature = method.getSignatureKey();
@@ -333,8 +363,8 @@ private List resolveMethodConflicts(Map methodTo
}
}
- // Sort methods using enhanced sorting logic
- return signatureToMethod.values().stream().sorted(new MethodDto.MethodComparator()).toList();
+ // Return methods in insertion order (already sorted from conflict resolution)
+ return new java.util.ArrayList<>(signatureToMethod.values());
}
/**
@@ -379,8 +409,6 @@ private void generateConstructors(
builderDef.getAllFieldsForBuilder(),
constructorAccessModifier);
classBuilder.addMethod(instanceConstructor);
-
- logger.debug(" Generated constructors for builder");
}
private MethodSpec createEmptyConstructor(ClassName dtoClass, Modifier accessModifier) {
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 1ee82151..80b0f326 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
@@ -258,7 +258,8 @@ public void debug(String message) {
}
/**
- * Logs a debug message with a formatted string.
+ * Logs a debug message with a formatted string. Only visible when enabled via -Averbose=true or
+ * -Asimplebuilder.verbose=true.
*
* @param format the format string
* @param args arguments referenced by the format specifiers in the format string
@@ -267,6 +268,31 @@ public void debug(String format, Object... args) {
logger.debug(format, args);
}
+ /**
+ * Starts a new hierarchical operation context for logging with formatted message.
+ *
+ * @param format the format string for the operation message
+ * @param args arguments referenced by the format specifiers
+ */
+ public void debugStartOperation(String format, Object... args) {
+ logger.debugStartOperation(format, args);
+ }
+
+ /** Ends the current hierarchical operation context for logging. */
+ public void debugEndOperation() {
+ logger.debugEndOperation();
+ }
+
+ /** Ends the current hierarchical operation context with a closing message for logging. */
+ public void debugEndOperation(String format, Object... args) {
+ logger.debugEndOperation(format, args);
+ }
+
+ /** Resets the indentation level to prevent cascading errors between processing runs. */
+ public void resetIndentation() {
+ logger.resetIndentation();
+ }
+
/**
* Logs a warning message without requiring a specific element context.
*
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 9d1fc307..409cfbc4 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
@@ -42,6 +42,9 @@ public class ProcessingLogger {
/** Flag indicating if debug logging is enabled. */
private final boolean debugEnabled;
+ /** Thread-local indentation level for hierarchical logging. */
+ private static final ThreadLocal indentationLevel = ThreadLocal.withInitial(() -> 0);
+
/**
* Constructs a new ProcessingLogger with the specified ProcessingEnvironment. The Messager is
* used to report errors, warnings, and other notices during annotation processing. Debug logging
@@ -50,11 +53,19 @@ public class ProcessingLogger {
* @param processingEnv the processing environment providing messager and options
*/
public ProcessingLogger(ProcessingEnvironment processingEnv) {
+ // Reset ThreadLocal state to ensure clean state between test runs
+ resetThreadLocalState();
+
this.messager = processingEnv.getMessager();
CompilerArgumentsReader reader = new CompilerArgumentsReader(processingEnv);
this.debugEnabled = reader.readBooleanValue(CompilerArgumentsEnum.VERBOSE);
}
+ /** Resets ThreadLocal state to ensure clean state between test runs. */
+ private void resetThreadLocalState() {
+ indentationLevel.set(0);
+ }
+
/**
* Reports an error with a formatted message.
*
@@ -75,6 +86,20 @@ public void info(String message) {
messager.printMessage(Diagnostic.Kind.NOTE, message);
}
+ /**
+ * Formats a message with hierarchical indentation and proper spacing for alignment. Used by info
+ * and warning methods when debug mode is enabled.
+ *
+ * @param message the message to format
+ * @param spaces the number of spaces to add before the hierarchy for alignment
+ * @return the formatted message with proper indentation and spacing
+ */
+ private String formatHierarchicalMessage(String message, int spaces) {
+ String prefix = getCurrentIndentationLevel() > 0 ? "└─ " : "";
+ String spacing = " ".repeat(spaces);
+ return String.format("%s%s", spacing, formatWithIndentationNoDebug(message, prefix));
+ }
+
/**
* Posts an info-level message with a formatted string.
*
@@ -82,7 +107,16 @@ public void info(String message) {
* @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));
+ String message = String.format(format, args);
+ if (debugEnabled) {
+ // When debug is enabled, add spaces to align with [DEBUG] prefix (which is 6 characters
+ // longer)
+ String indentedMessage = formatHierarchicalMessage(message, 8); // Add 8 spaces for alignment
+ messager.printMessage(Diagnostic.Kind.NOTE, indentedMessage);
+ } else {
+ // When debug is disabled, use flat formatting (current behavior)
+ messager.printMessage(Diagnostic.Kind.NOTE, message);
+ }
}
/**
@@ -94,8 +128,8 @@ public void info(String format, Object... args) {
*/
public void debug(String message) {
if (debugEnabled) {
- String formatWithDebug = "[DEBUG] " + message;
- messager.printMessage(Diagnostic.Kind.OTHER, formatWithDebug);
+ String indentedMessage = formatWithIndentation(message);
+ messager.printMessage(Diagnostic.Kind.OTHER, indentedMessage);
}
}
@@ -108,8 +142,9 @@ public void debug(String message) {
*/
public void debug(String format, Object... args) {
if (debugEnabled) {
- String formatWithDebug = "[DEBUG] " + format;
- messager.printMessage(Diagnostic.Kind.OTHER, String.format(formatWithDebug, args));
+ String message = String.format(format, args);
+ String indentedMessage = formatWithIndentation(message);
+ messager.printMessage(Diagnostic.Kind.OTHER, indentedMessage);
}
}
@@ -121,7 +156,16 @@ public void debug(String format, Object... args) {
* @param args arguments referenced by the format specifiers in the format string
*/
public void warning(String format, Object... args) {
- messager.printMessage(Diagnostic.Kind.WARNING, String.format(format, args));
+ String message = String.format(format, args);
+ if (debugEnabled) {
+ // When debug is enabled, add spaces to align with [DEBUG] prefix (which is 5 characters
+ // longer)
+ String indentedMessage = formatHierarchicalMessage(message, 5); // Add 5 spaces for alignment
+ messager.printMessage(Diagnostic.Kind.WARNING, indentedMessage);
+ } else {
+ // When debug is disabled, print flat
+ messager.printMessage(Diagnostic.Kind.WARNING, message);
+ }
}
/**
@@ -132,6 +176,144 @@ public void warning(String format, Object... args) {
* @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);
+ String message = String.format(format, args);
+ if (debugEnabled) {
+ // When debug is enabled, add spaces to align with [DEBUG] prefix (which is 5 characters
+ // longer)
+ String indentedMessage = formatHierarchicalMessage(message, 5); // Add 5 spaces for alignment
+ messager.printMessage(Diagnostic.Kind.WARNING, indentedMessage, e);
+ } else {
+ // When debug is disabled, print flat
+ messager.printMessage(Diagnostic.Kind.WARNING, message, e);
+ }
+ }
+
+ /**
+ * Formats a message with appropriate indentation based on current context.
+ *
+ * @param message the message to format
+ * @param prefix the prefix to add (e.g., "├─ ", "└─ ") or empty string for no prefix
+ * @return the formatted message with indentation
+ */
+ private String formatWithIndentation(String message, String prefix) {
+ int level = indentationLevel.get();
+ if (level == 0) {
+ // At level 0, no indentation or prefix
+ return "[DEBUG] " + message;
+ }
+
+ // Use │ characters with proper spacing for better visual connection between hierarchical levels
+ StringBuilder indent = new StringBuilder();
+ for (int i = 0; i < level - 1; i++) {
+ indent.append("│ ");
+ }
+ // Add the specified prefix
+ indent.append(prefix);
+ return "[DEBUG] " + indent + message;
+ }
+
+ /**
+ * Formats a message with indentation but without [DEBUG] prefix for INFO/WARNING messages.
+ *
+ * @param message the message to format
+ * @param prefix the prefix to add (e.g., "├─ ", "└─ ") or empty string for no prefix
+ * @return the formatted message with indentation but without [DEBUG] prefix
+ */
+ private String formatWithIndentationNoDebug(String message, String prefix) {
+ int level = indentationLevel.get();
+ if (level == 0) {
+ // At level 0, no indentation or prefix
+ return message;
+ }
+
+ // Use │ characters with proper spacing for better visual connection between hierarchical levels
+ StringBuilder indent = new StringBuilder();
+ for (int i = 0; i < level - 1; i++) {
+ indent.append("│ ");
+ }
+ // Add the specified prefix
+ indent.append(prefix);
+ return indent + message;
+ }
+
+ /**
+ * Formats a message with ├─ prefix for debug messages.
+ *
+ * @param message the message to format
+ * @return the formatted message with indentation and ├─ prefix
+ */
+ private String formatWithIndentation(String message) {
+ return formatWithIndentation(message, "├─ ");
+ }
+
+ /**
+ * Starts a new hierarchical operation context, increasing indentation for subsequent debug
+ * messages. This should be called before starting a major operation that has sub-operations.
+ *
+ * @param formatClosingMessage the format string for the operation message
+ * @param args arguments referenced by the format specifiers
+ */
+ public void debugStartOperation(String formatClosingMessage, Object... args) {
+ int currentLevel = indentationLevel.get();
+
+ // Log the operation start message with proper prefix handling
+ if (debugEnabled) {
+ String operationMessage = formatWithIndentation(String.format(formatClosingMessage, args));
+ messager.printMessage(Diagnostic.Kind.NOTE, operationMessage);
+ }
+
+ // Increase indentation for subsequent messages
+ indentationLevel.set(currentLevel + 1);
+ }
+
+ /**
+ * Ends the current hierarchical operation context, decreasing indentation. This should be called
+ * after completing an operation started with startOperation.
+ */
+ public void debugEndOperation() {
+ int currentLevel = indentationLevel.get();
+ if (currentLevel > 0) {
+ indentationLevel.set(currentLevel - 1);
+ // No need to log "Operation completed" - it's redundant and adds noise
+ }
+ }
+
+ /**
+ * Ends the current hierarchical operation context with a closing message, decreasing indentation.
+ * This should be called after completing an operation started with startOperation when you want
+ * to log a closing message with the proper tree structure (using └─ for the last operation).
+ *
+ * @param formatClosingMessage the format string for the closing message
+ * @param args arguments referenced by the format specifiers
+ */
+ public void debugEndOperation(String formatClosingMessage, Object... args) {
+ int currentLevel = indentationLevel.get();
+
+ // Log the closing message with └─ to indicate it's the last operation at this level
+ if (debugEnabled) {
+ String closingMessage = String.format(formatClosingMessage, args);
+ String operationMessage = formatWithIndentation(closingMessage, "└─ ");
+ messager.printMessage(Diagnostic.Kind.NOTE, operationMessage);
+ }
+
+ // Decrease indentation level
+ if (currentLevel > 0) {
+ indentationLevel.set(currentLevel - 1);
+ }
+ }
+
+ /**
+ * Resets the indentation level to zero to prevent cascading errors between processing runs. This
+ * should be called at the end of each processing round.
+ */
+ public void resetIndentation() {
+ indentationLevel.set(0);
+ // Clean up ThreadLocal to prevent memory leaks
+ indentationLevel.remove();
+ }
+
+ /** Gets the current indentation level for debugging purposes. */
+ public int getCurrentIndentationLevel() {
+ return indentationLevel.get();
}
}
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 b704d01c..1a707c56 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
@@ -1,3 +1,27 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package org.javahelpers.simple.builders.processor;
import static com.google.testing.compile.CompilationSubject.assertThat;
@@ -62,14 +86,61 @@ void shouldLogDebugMessagesWhenVerboseModeEnabled() {
// Then: Compilation succeeds and debug messages are present
assertThat(compilation).succeeded();
- // Verify key debug messages are logged
+ // Verify key debug messages are logged with hierarchical format
ProcessorAsserts.assertHadNoteContaining(
compilation,
+ "[DEBUG] Starting BuilderProcessor...",
+ "[DEBUG] Loaded global configuration from compiler arguments: BuilderConfiguration[]",
+ "[DEBUG] Initializing generator registry",
+ "[DEBUG] ├─ Loaded 14 method generators and 8 builder enhancers total",
+ "[DEBUG] └─ Initialized GeneratorRegistry with 14 method generators and 8 builder",
"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");
+ "[DEBUG] simple-builders: Processing round started. Found 1 annotated elements.",
+ "[DEBUG] Processing element: VerboseTest",
+ "[DEBUG] ├─ Resolving configuration for element: VerboseTest",
+ "[DEBUG] │ ├─ Template annotations ignored because @SimpleBuilder present",
+ "[DEBUG] │ └─ Resulting configuration resolved: BuilderConfiguration[",
+ "[DEBUG] ├─ Extracting builder definition from: test.VerboseTest",
+ "[DEBUG] │ ├─ Builder will be generated as: test.VerboseTestBuilder",
+ "[DEBUG] │ ├─ Analysing setters for finding fields",
+ "[DEBUG] │ │ ├─ Analyzing method: setName with 1 parameter(s)",
+ "[DEBUG] │ │ │ ├─ Processing method generators",
+ "[DEBUG] │ │ │ │ ├─ Applying: BasicSetterGenerator (priority: 100)",
+ "[DEBUG] │ │ │ │ ├─ Applying: StringFormatHelperGenerator (priority: 80)",
+ "[DEBUG] │ │ │ │ ├─ Applying: SupplierMethodGenerator (priority: 60)",
+ "[DEBUG] │ │ │ │ ├─ Applying: StringBuilderConsumerGenerator (priority: 45)",
+ "[DEBUG] │ │ │ │ └─ Generated 4 methods",
+ "[DEBUG] │ │ │ └─ Adding field: name (type: java.lang.String)",
+ "[DEBUG] │ │ └─ Processed 1 possible setters: added 1 fields, skipped 0",
+ "[DEBUG] │ ├─ Processing class based enhancer",
+ "[DEBUG] │ │ ├─ Applying: ClassJavaDocEnhancer (priority: 200)",
+ "[DEBUG] │ │ ├─ Applying: GeneratedAnnotationEnhancer (priority: 120)",
+ "[DEBUG] │ │ ├─ Applying: BuilderImplementationAnnotationEnhancer (priority: 115)",
+ "[DEBUG] │ │ ├─ Applying: CoreMethodsEnhancer (priority: 100)",
+ "[DEBUG] │ │ ├─ Applying: WithInterfaceEnhancer (priority: 95)",
+ "[DEBUG] │ │ ├─ Applying: InterfaceEnhancer (priority: 90)",
+ "[DEBUG] │ │ ├─ Applying: ConditionalEnhancer (priority: 80)",
+ "[DEBUG] │ │ └─ Applied 7 builder enhancers",
+ "[DEBUG] │ └─ Builder definition extracted: VerboseTestBuilder",
+ "[DEBUG] ├─ Code generation for builder: VerboseTestBuilder",
+ "[DEBUG] │ ├─ Class builder created",
+ "[DEBUG] │ ├─ Class metadata added",
+ "[DEBUG] │ ├─ Generating 0 constructor fields and 1 setter fields",
+ "[DEBUG] │ │ └─ Fields added: 1 fields",
+ "[DEBUG] │ ├─ Adding Methods for 9 candidates",
+ "[DEBUG] │ │ ├─ Resolved 9 methods after conflict resolution",
+ "[DEBUG] │ │ └─ 9 Methods added",
+ "[DEBUG] │ ├─ Constructors added",
+ "[DEBUG] │ ├─ Generating 1 nested type(s)",
+ "[DEBUG] │ │ ├─ Generated nested type: With",
+ "[DEBUG] │ │ └─ Nested types added",
+ "[DEBUG] │ ├─ Class-level annotations added",
+ "[DEBUG] │ ├─ Writing builder class to file: test.VerboseTestBuilder",
+ "[DEBUG] │ └─ Successfully generated builder: VerboseTestBuilder",
+ "[DEBUG] ├─ Jackson module entry added",
+ "[DEBUG] └─ Generated builder with 1 fields and 5 methods for VerboseTestBuilder",
+ "simple-builders: Successfully generated 1 builder(s) in this processing round",
+ "");
}
@Test
@@ -2641,7 +2712,7 @@ void shouldHandleOverloadedSettersForSameFieldWithoutConflicts() {
generatedCode,
"public OverloadedNamesBuilder names(List names)",
"public OverloadedNamesBuilder names(String... names)",
- "public OverloadedNamesBuilder names(Supplier> namesSupplier)");
+ "public OverloadedNamesBuilder names(Supplier namesSupplier)");
}
@Test
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java
index f846668d..45012b2f 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java
@@ -137,11 +137,6 @@ public PersonDto(String name, int age, Optional email,
String generatedCode = loadGeneratedSource(compilation, "PersonDtoBuilder");
- // Debug output: Print generated code for comparison when test fails
- System.out.println("=== Generated PersonDtoBuilder ===");
- System.out.println(generatedCode);
- System.out.println("=== End of Generated Code ===");
-
// This test uses full code comparison to ensure ALL features are generated.
// When a new feature is added, this expected code MUST be updated or the test will fail.
// This will catch when new features like add2FieldName are added but not included here.
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/MethodConflictResolutionTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/MethodConflictResolutionTest.java
index 6880d6c0..e8811508 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/MethodConflictResolutionTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/MethodConflictResolutionTest.java
@@ -1,3 +1,27 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package org.javahelpers.simple.builders.processor;
import static com.google.testing.compile.CompilationSubject.assertThat;