diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java index 0a9d5b16..e6ca1145 100644 --- a/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java +++ b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java @@ -61,6 +61,7 @@ * usingArrayListBuilderWithElementBuilders, usingHashSetBuilder, * usingHashSetBuilderWithElementBuilders, usingHashMapBuilder (all default: true) *
  • Integration: generateWithInterface (default: true) + *
  • Documentation: generateJavaDoc (default: true) * * *

    This annotation is itself a built-in {@link Template}: it is meta-annotated with @@ -666,6 +667,18 @@ */ String jacksonModulePackage() default ""; + /** + * Generate Javadoc comments on the generated builder class and its members.
    + * When disabled, no class, field, constructor or method Javadoc is emitted, producing smaller + * generated files. + * + *

    Default: ENABLED
    + * Compiler option: -Asimplebuilder.generateJavaDoc + * + * @return the option state for generating Javadoc + */ + OptionState generateJavaDoc() default OptionState.UNSET; + // === Naming === /** * Suffix to append to the DTO name to generate the builder class name.
    diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleMinimalBuilder.java b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleMinimalBuilder.java index c3968263..63195002 100644 --- a/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleMinimalBuilder.java +++ b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleMinimalBuilder.java @@ -90,6 +90,7 @@ usingBuilderImplementationAnnotation = OptionState.DISABLED, usingJacksonDeserializerAnnotation = OptionState.DISABLED, generateJacksonModule = OptionState.DISABLED, + generateJavaDoc = OptionState.DISABLED, copyTypeAnnotations = OptionState.DISABLED, implementsBuilderBase = OptionState.DISABLED, builderSuffix = "Builder", diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 04d3981a..5f07e0a4 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -19,6 +19,7 @@ Simple-builders supports fine-grained configuration through the `@SimpleBuilder. - [Collection Helpers](#collection-helpers) - [Component Filtering](#component-filtering) - [Integration](#integration) + - [Documentation](#documentation) - [Reliability](#reliability) - [Performance Tracking](#performance-tracking) - [Examples](#examples) @@ -112,7 +113,8 @@ Create reusable configuration presets with custom template annotations. The buil usingBuilderImplementationAnnotation = OptionState.DISABLED, implementsBuilderBase = OptionState.DISABLED, usingJacksonDeserializerAnnotation = OptionState.DISABLED, - generateJacksonModule = OptionState.DISABLED + generateJacksonModule = OptionState.DISABLED, + generateJavaDoc = OptionState.DISABLED )) @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) @@ -858,6 +860,30 @@ This is highly recommended to ensure deterministic output location and avoid spl --- +### Documentation + +#### `generateJavaDoc` + +**Default**: `ENABLED` | **Compiler Option**: `-Asimplebuilder.generateJavaDoc=ENABLED|DISABLED` + +Controls whether the processor emits Javadoc comments on the generated builder class, its fields, constructors and methods. + +**When ENABLED** (default): +The generated builder contains Javadoc blocks explaining the purpose of the class, setters, `build()`, `create()` and any helper methods. + +**When DISABLED**: +No Javadoc is emitted. This produces smaller generated source files and is useful when generated code is committed to version control and Javadoc noise is undesirable. + +**Example**: +```java +@SimpleBuilder(options = @SimpleBuilder.Options(generateJavaDoc = OptionState.DISABLED)) +public class PersonDto { + private String name; +} +``` + +--- + ### Naming #### `builderSuffix` @@ -1069,7 +1095,8 @@ The built-in `@SimpleMinimalBuilder` is the simplest way to get a lightweight bu usingBuilderImplementationAnnotation = OptionState.DISABLED, implementsBuilderBase = OptionState.DISABLED, usingJacksonDeserializerAnnotation = OptionState.DISABLED, - generateJacksonModule = OptionState.DISABLED + generateJacksonModule = OptionState.DISABLED, + generateJavaDoc = OptionState.DISABLED )) @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) @@ -1337,6 +1364,9 @@ methodAccess = AccessModifier.PRIVATE -Asimplebuilder.usingGeneratedAnnotation=ENABLED|DISABLED -Asimplebuilder.usingBuilderImplementationAnnotation=ENABLED|DISABLED +# Documentation +-Asimplebuilder.generateJavaDoc=ENABLED|DISABLED + # Naming -Asimplebuilder.builderSuffix=CustomSuffix -Asimplebuilder.setterSuffix=customPrefix @@ -1385,7 +1415,10 @@ methodAccess = AccessModifier.PRIVATE usingGeneratedAnnotation = OptionState.ENABLED, usingBuilderImplementationAnnotation = OptionState.ENABLED, usingJacksonDeserializerAnnotation = OptionState.ENABLED, - + + // Documentation + generateJavaDoc = OptionState.ENABLED, + // Naming builderSuffix = "Builder", setterSuffix = "" diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/CustomerDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/CustomerDtoBuilder.java index bbcebcd2..b8c2c25f 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/CustomerDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/CustomerDtoBuilder.java @@ -8,54 +8,16 @@ import org.javahelpers.simple.builders.core.util.BuilderToStringStyle; import org.javahelpers.simple.builders.core.util.TrackedValue; -/** - * Builder for {@code org.javahelpers.simple.builders.example.CustomerDto}. - *

    - * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.CustomerDto 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. - * - *

    Example:

    - * - *
    {@code
    - * CustomerDto result = CustomerDtoBuilder.create()
    - *     .email("example value")
    - *     .id(42L)
    - *     .name("example value")
    - *     .tags(List.of("example value"))
    - *     .build();
    - * }
    - */ public class CustomerDtoBuilder { - /** - * Tracked value for email: email. - */ private TrackedValue email = unsetValue(); - /** - * Tracked value for id: id. - */ private TrackedValue id = unsetValue(); - /** - * Tracked value for name: name. - */ private TrackedValue name = unsetValue(); - /** - * Tracked value for tags: tags. - */ private TrackedValue> tags = unsetValue(); - /** - * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.CustomerDto}. - */ public CustomerDtoBuilder() { } - /** - * Initialisation of builder for {@code org.javahelpers.simple.builders.example.CustomerDto} by a instance. - * - * @param instance object instance for initialisiation - */ public CustomerDtoBuilder(CustomerDto instance) { this.email = initialValue(instance.getEmail()); this.id = initialValue(instance.getId()); @@ -63,105 +25,30 @@ public CustomerDtoBuilder(CustomerDto instance) { this.tags = initialValue(instance.getTags()); } - /** - * Creating a new builder for {@code org.javahelpers.simple.builders.example.CustomerDto}. - * - *

    Example:

    - * - *
    {@code
    -   * CustomerDtoBuilder builder = CustomerDtoBuilder.create();
    -   * }
    - * - * @return builder for {@code org.javahelpers.simple.builders.example.CustomerDto} - */ public static CustomerDtoBuilder create() { return new CustomerDtoBuilder(); } - /** - * Sets the value for email. - *

    - * Generated from setter {@link CustomerDto#setEmail(String) setEmail(String email)} - * - *

    Example:

    - * - *
    {@code
    -   * builder.email("example value");
    -   * }
    - * - * @param email email - * @return current instance of builder - */ public CustomerDtoBuilder email(String email) { this.email = changedValue(email); return this; } - /** - * Sets the value for id. - *

    - * Generated from setter {@link CustomerDto#setId(Long) setId(Long id)} - * - *

    Example:

    - * - *
    {@code
    -   * builder.id(42L);
    -   * }
    - * - * @param id id - * @return current instance of builder - */ public CustomerDtoBuilder id(Long id) { this.id = changedValue(id); return this; } - /** - * Sets the value for name. - *

    - * Generated from setter {@link CustomerDto#setName(String) setName(String name)} - * - *

    Example:

    - * - *
    {@code
    -   * builder.name("example value");
    -   * }
    - * - * @param name name - * @return current instance of builder - */ public CustomerDtoBuilder name(String name) { this.name = changedValue(name); return this; } - /** - * Sets the value for tags. - *

    - * Generated from setter {@link CustomerDto#setTags(List) setTags(List tags)} - * - *

    Example:

    - * - *
    {@code
    -   * builder.tags(List.of("example value"));
    -   * }
    - * - * @param tags tags - * @return current instance of builder - */ public CustomerDtoBuilder tags(List tags) { this.tags = changedValue(tags); return this; } - /** - * Validates that the email field is not null or empty. - *

    - * Generated from setter {@link CustomerDto#setEmail(String) setEmail(String email)} - * - * @return this builder instance for chaining - * @throws IllegalArgumentException if email is null or empty - */ CustomerDtoBuilder validateEmail() { if (!email.isSet() || email.value().trim().isEmpty()) { throw new IllegalArgumentException("Email cannot be null or empty"); @@ -169,14 +56,6 @@ CustomerDtoBuilder validateEmail() { return this; } - /** - * Validates that the name field is not null or empty. - *

    - * Generated from setter {@link CustomerDto#setName(String) setName(String name)} - * - * @return this builder instance for chaining - * @throws IllegalArgumentException if name is null or empty - */ CustomerDtoBuilder validateName() { if (!name.isSet() || name.value().trim().isEmpty()) { throw new IllegalArgumentException("Name cannot be null or empty"); @@ -184,15 +63,6 @@ CustomerDtoBuilder validateName() { return this; } - /** - * Builds the configured DTO instance. - * - *

    Example:

    - * - *
    {@code
    -   * CustomerDto result = builder.build();
    -   * }
    - */ public CustomerDto build() { CustomerDto result = new CustomerDto(); this.email.ifSet(result::setEmail); @@ -202,11 +72,6 @@ public CustomerDto build() { 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("email", this.email) 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 2e496d65..90fea6b4 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 @@ -24,7 +24,6 @@ package org.javahelpers.simple.builders.processor; -import static org.javahelpers.simple.builders.processor.model.core.BuilderToGenerationTypeMapper.toRenderingDto; import static org.javahelpers.simple.builders.processor.processing.BuilderDefinitionCreator.extractFromElement; import static org.javahelpers.simple.builders.processor.processing.logging.PerformanceTracker.PHASE_BUILDER_DEFINITION_EXTRACTION; import static org.javahelpers.simple.builders.processor.processing.logging.PerformanceTracker.PHASE_CODE_GENERATION; @@ -55,6 +54,7 @@ import org.javahelpers.simple.builders.processor.generators.integration.JacksonModuleGenerator; import org.javahelpers.simple.builders.processor.model.core.BuilderConfiguration; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; +import org.javahelpers.simple.builders.processor.model.core.BuilderToGenerationTypeMapper; import org.javahelpers.simple.builders.processor.model.core.GenerationTargetClassDto; import org.javahelpers.simple.builders.processor.model.type.TypeNameList; import org.javahelpers.simple.builders.processor.model.type.TypeNameMap; @@ -263,11 +263,13 @@ private void process(Element annotatedElement, BuilderConfiguration config) // Track DTO Mapping tracker.startPhase(); - GenerationTargetClassDto renderingDto = toRenderingDto(builderDef); + GenerationTargetClassDto renderingDto = + new BuilderToGenerationTypeMapper(config).toRenderingDto(builderDef); tracker.endPhase(PHASE_DTO_MAPPING); // Track Code Generation (parent phase; sub-phases tracked inside RoasterCodeGenerator) tracker.startPhase(); + codeGenerator.generateClass(renderingDto); tracker.endPhase(PHASE_CODE_GENERATION); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderConfiguration.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderConfiguration.java index b61c60d3..b007eadb 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderConfiguration.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderConfiguration.java @@ -62,6 +62,7 @@ * @param usingBuilderImplementationAnnotation Use BuilderImplementation annotation * @param implementsBuilderBase Implement IBuilderBase interface * @param generateWithInterface Generate With interface + * @param generateJavaDoc Generate Javadoc comments * @param builderSuffix Suffix for builder class name * @param setterSuffix Suffix for setter method names * @param strict Strict/fail-fast generation mode @@ -90,6 +91,7 @@ public record BuilderConfiguration( OptionState generateWithInterface, OptionState usingJacksonDeserializerAnnotation, OptionState generateJacksonModule, + OptionState generateJavaDoc, String jacksonModulePackage, String builderSuffix, String setterSuffix, @@ -120,6 +122,7 @@ public record BuilderConfiguration( .generateWithInterface(ENABLED) .usingJacksonDeserializerAnnotation(DISABLED) .generateJacksonModule(DISABLED) + .generateJavaDoc(ENABLED) .jacksonModulePackage(null) .builderSuffix("Builder") .setterSuffix("") @@ -155,6 +158,10 @@ public boolean shouldGenerateJacksonModule() { return generateJacksonModule == ENABLED; } + public boolean shouldGenerateJavaDoc() { + return generateJavaDoc == ENABLED; + } + public boolean shouldGenerateVarArgsHelpers() { return generateVarArgsHelpers == ENABLED; } @@ -299,6 +306,7 @@ public BuilderConfiguration merge(BuilderConfiguration other) { other.usingJacksonDeserializerAnnotation, this.usingJacksonDeserializerAnnotation)) .generateJacksonModule( mergeOptionState(other.generateJacksonModule, this.generateJacksonModule)) + .generateJavaDoc(mergeOptionState(other.generateJavaDoc, this.generateJavaDoc)) .jacksonModulePackage(mergeString(other.jacksonModulePackage, this.jacksonModulePackage)) .builderSuffix(mergeString(other.builderSuffix, this.builderSuffix)) .setterSuffix(mergeString(other.setterSuffix, this.setterSuffix)) @@ -365,6 +373,7 @@ public String toString() { .appendValueIfSet("generateWithInterface", generateWithInterface) .appendValueIfSet("usingJacksonDeserializerAnnotation", usingJacksonDeserializerAnnotation) .appendValueIfSet("generateJacksonModule", generateJacksonModule) + .appendValueIfSet("generateJavaDoc", generateJavaDoc) .appendIfNotEmpty("jacksonModulePackage", jacksonModulePackage) .appendIfNotEmpty("builderSuffix", builderSuffix) .appendIfNotEmpty("setterSuffix", setterSuffix) @@ -443,6 +452,7 @@ public static class Builder { private OptionState generateWithInterface = OptionState.UNSET; private OptionState usingJacksonDeserializerAnnotation = OptionState.UNSET; private OptionState generateJacksonModule = OptionState.UNSET; + private OptionState generateJavaDoc = OptionState.UNSET; private String jacksonModulePackage = null; // === Naming === @@ -523,6 +533,16 @@ public Builder generateJacksonModule(boolean value) { return this; } + public Builder generateJavaDoc(OptionState value) { + this.generateJavaDoc = value; + return this; + } + + public Builder generateJavaDoc(boolean value) { + this.generateJavaDoc = value ? ENABLED : DISABLED; + return this; + } + public Builder jacksonModulePackage(String value) { this.jacksonModulePackage = StringUtils.trimToNull(value); return this; @@ -733,6 +753,7 @@ public BuilderConfiguration build() { generateWithInterface, usingJacksonDeserializerAnnotation, generateJacksonModule, + generateJavaDoc, jacksonModulePackage, builderSuffix, setterSuffix, diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java index 7f4a0160..530189f6 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java @@ -27,6 +27,7 @@ import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; +import org.javahelpers.simple.builders.processor.model.method.ConstructorDto; import org.javahelpers.simple.builders.processor.model.method.MethodCodePlaceholder; import org.javahelpers.simple.builders.processor.model.method.MethodCodeStringPlaceholder; import org.javahelpers.simple.builders.processor.model.method.MethodCodeTypePlaceholder; @@ -42,11 +43,22 @@ *

    This mapper copies all rendering-relevant fields from the generation DTOs to the rendering * DTOs. Generation-only fields ({@code sourceFieldName}, {@code constructorField}, {@code * exampleChainFragment}) are not mapped. + * + *

    The effective {@link BuilderConfiguration} controls whether Javadoc is emitted: when {@code + * generateJavaDoc} is disabled, all {@link JavadocDto} instances are left out of the rendering DTO + * so the code generator can remain a pure renderer. */ public class BuilderToGenerationTypeMapper { - private BuilderToGenerationTypeMapper() { - // Utility class - prevent instantiation + private final BuilderConfiguration configuration; + + /** + * Creates a mapper for the given effective builder configuration. + * + * @param configuration the effective builder configuration + */ + public BuilderToGenerationTypeMapper(BuilderConfiguration configuration) { + this.configuration = configuration; } /** @@ -59,20 +71,22 @@ private BuilderToGenerationTypeMapper() { * @param builderDto the generation DTO * @return the rendering DTO for code generation */ - public static GenerationTargetClassDto toRenderingDto(BuilderDefinitionDto builderDto) { + public GenerationTargetClassDto toRenderingDto(BuilderDefinitionDto builderDto) { GenerationTargetClassDto renderingDto = new GenerationTargetClassDto(); renderingDto.setTypeName(builderDto.getTypeName()); renderingDto.setClassAccessModifier(builderDto.getClassAccessModifier()); renderingDto.setSuperType(builderDto.getSuperType()); - renderingDto.setClassJavadoc(builderDto.getClassJavadoc()); + renderingDto.setClassJavadoc( + configuration.shouldGenerateJavaDoc() ? builderDto.getClassJavadoc() : null); - // Copy class fields - builderDto.getClassFields().forEach(renderingDto::addClassField); + builderDto.getClassFields().stream() + .map(this::toRenderingClassField) + .forEach(renderingDto::addClassField); - // Copy constructors - builderDto.getConstructors().forEach(renderingDto::addConstructor); + builderDto.getConstructors().stream() + .map(this::toRenderingConstructor) + .forEach(renderingDto::addConstructor); - // Copy generics builderDto.getGenerics().forEach(renderingDto::addGeneric); // Copy imports @@ -101,10 +115,9 @@ public static GenerationTargetClassDto toRenderingDto(BuilderDefinitionDto build renderingDto.addMethod(toMethodDto(classMethod)); } - // Map and copy nested types from enhancers - for (BuilderNestedTypeDto builderNestedType : builderDto.getNestedTypes()) { - renderingDto.addNestedType(toNestedTypeDto(builderNestedType)); - } + builderDto + .getNestedTypes() + .forEach(nestedType -> renderingDto.addNestedType(toNestedTypeDto(nestedType))); return renderingDto; } @@ -119,24 +132,15 @@ public static GenerationTargetClassDto toRenderingDto(BuilderDefinitionDto build * @param classMethod the generation DTO to map * @return a new {@link MethodDto} with all rendering fields copied */ - private static MethodDto toMethodDto(BuilderMethodDto classMethod) { + private MethodDto toMethodDto(BuilderMethodDto classMethod) { MethodDto method = new MethodDto(classMethod.getMethodName(), classMethod.getReturnType()); method.setModifier(classMethod.getModifier().orElse(null)); method.setStatic(classMethod.isStatic()); method.setOrdering(classMethod.getOrdering()); - // Enrich javadoc with pre-built source description if source field is known - JavadocDto javadoc = classMethod.getJavadoc(); - if (StringUtils.isNotBlank(classMethod.getSourceFieldName())) { - if (javadoc == null) { - javadoc = new JavadocDto(); - } - String sourceDescription = classMethod.getSourceDescription(); - if (sourceDescription != null) { - javadoc.appendDescriptionLine(sourceDescription); - } + if (configuration.shouldGenerateJavaDoc()) { + method.setJavadoc(buildMethodJavadoc(classMethod)); } - method.setJavadoc(javadoc); classMethod.getAnnotations().forEach(method::addAnnotation); classMethod.getParameters().forEach(method::addParameter); classMethod.getGenericParameters().forEach(method::addGenericParameter); @@ -161,6 +165,53 @@ private static MethodDto toMethodDto(BuilderMethodDto classMethod) { return method; } + /** + * Returns the given class field, clearing its Javadoc when Javadoc generation is disabled. + * + * @param classField the source class field + * @return the class field ready for rendering + */ + private ClassFieldDto toRenderingClassField(ClassFieldDto classField) { + if (!configuration.shouldGenerateJavaDoc()) { + classField.setJavadoc(null); + } + return classField; + } + + /** + * Returns the given constructor, clearing its Javadoc when Javadoc generation is disabled. + * + * @param constructor the source constructor + * @return the constructor ready for rendering + */ + private ConstructorDto toRenderingConstructor(ConstructorDto constructor) { + if (!configuration.shouldGenerateJavaDoc()) { + constructor.setJavadoc(null); + } + return constructor; + } + + /** + * Builds the {@link JavadocDto} for a method, appending the pre-built source description when a + * source field is known. + * + * @param classMethod the generation method DTO + * @return the Javadoc to render, or {@code null} when none is present + */ + private JavadocDto buildMethodJavadoc(BuilderMethodDto classMethod) { + JavadocDto javadoc = classMethod.getJavadoc(); + if (StringUtils.isNotBlank(classMethod.getSourceFieldName())) { + if (javadoc == null) { + javadoc = new JavadocDto(); + } + String sourceDescription = classMethod.getSourceDescription(); + if (sourceDescription != null) { + javadoc.appendDescriptionLine(sourceDescription); + } + } + return javadoc; + } + /** * Maps a {@link BuilderNestedTypeDto} (generation DTO) to a {@link NestedTypeDto} (rendering * DTO). @@ -171,12 +222,14 @@ private static MethodDto toMethodDto(BuilderMethodDto classMethod) { * @param builderNestedType the generation DTO to map * @return a new {@link NestedTypeDto} with all rendering fields copied */ - public static NestedTypeDto toNestedTypeDto(BuilderNestedTypeDto builderNestedType) { + public NestedTypeDto toNestedTypeDto(BuilderNestedTypeDto builderNestedType) { NestedTypeDto nestedType = new NestedTypeDto(); nestedType.setTypeName(builderNestedType.getTypeName()); nestedType.setKind(builderNestedType.getKind()); nestedType.setVisibility(builderNestedType.getVisibility()); - nestedType.setJavadoc(builderNestedType.getJavadoc()); + if (configuration.shouldGenerateJavaDoc()) { + nestedType.setJavadoc(builderNestedType.getJavadoc()); + } builderNestedType.getAnnotations().forEach(nestedType::addAnnotation); builderNestedType.getMethods().forEach(method -> nestedType.addMethod(toMethodDto(method))); return nestedType; diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderConfigurationReader.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderConfigurationReader.java index 49398368..607b968c 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderConfigurationReader.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderConfigurationReader.java @@ -332,6 +332,7 @@ private BuilderConfiguration parseOptionsFromMirror(AnnotationMirror optionsMirr builder.usingJacksonDeserializerAnnotation(OptionState.valueOf(enumValue)); case "generateJacksonModule" -> builder.generateJacksonModule(OptionState.valueOf(enumValue)); + case "generateJavaDoc" -> builder.generateJavaDoc(OptionState.valueOf(enumValue)); case "jacksonModulePackage" -> builder.jacksonModulePackage(value.toString()); case "builderSuffix" -> builder.builderSuffix(value.toString()); case "setterSuffix" -> builder.setterSuffix(value.toString()); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsEnum.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsEnum.java index 6dfcbd60..9f45f6f4 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsEnum.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsEnum.java @@ -113,6 +113,9 @@ public enum CompilerArgumentsEnum { /** Option for Jackson Module generation. */ GENERATE_JACKSON_MODULE("generateJacksonModule"), + /** Option for Javadoc generation on the generated builder. */ + GENERATE_JAVADOC("generateJavaDoc"), + /** Option for Jackson Module package name. */ JACKSON_MODULE_PACKAGE("jacksonModulePackage"), diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsReader.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsReader.java index 884c2820..967265ad 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsReader.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/CompilerArgumentsReader.java @@ -171,6 +171,7 @@ public BuilderConfiguration readBuilderConfiguration() { .usingJacksonDeserializerAnnotation( readOptionState(CompilerArgumentsEnum.USING_JACKSON_DESERIALIZER_ANNOTATION)) .generateJacksonModule(readOptionState(CompilerArgumentsEnum.GENERATE_JACKSON_MODULE)) + .generateJavaDoc(readOptionState(CompilerArgumentsEnum.GENERATE_JAVADOC)) .jacksonModulePackage(readValue(CompilerArgumentsEnum.JACKSON_MODULE_PACKAGE)) .builderSuffix(readValue(CompilerArgumentsEnum.BUILDER_SUFFIX)) .setterSuffix(readValue(CompilerArgumentsEnum.SETTER_SUFFIX)) diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java index 5b7d02ea..ea9f5470 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java @@ -81,6 +81,8 @@ void allConfigurationOptions_MustBeSettableViaBuilder() { .generateWithInterface(OptionState.ENABLED) .usingJacksonDeserializerAnnotation(OptionState.ENABLED) .generateJacksonModule(OptionState.ENABLED) + // Documentation + .generateJavaDoc(OptionState.ENABLED) // Naming .builderSuffix("Builder") .setterSuffix("") @@ -110,6 +112,7 @@ void allConfigurationOptions_MustBeSettableViaBuilder() { assertEquals(OptionState.ENABLED, config.generateWithInterface()); assertEquals(OptionState.ENABLED, config.usingJacksonDeserializerAnnotation()); assertEquals(OptionState.ENABLED, config.generateJacksonModule()); + assertEquals(OptionState.ENABLED, config.generateJavaDoc()); assertEquals("Builder", config.getBuilderSuffix()); assertEquals("", config.getSetterSuffix()); } @@ -215,6 +218,7 @@ public Address() {} "-Asimplebuilder.implementsBuilderBase=false", "-Asimplebuilder.generateWithInterface=false", "-Asimplebuilder.usingJacksonDeserializerAnnotation=false", + "-Asimplebuilder.generateJavaDoc=false", "-Asimplebuilder.builderSuffix=CustomBuilder", "-Asimplebuilder.setterSuffix=with") .compile(nestedDto, addressDto, source); @@ -293,6 +297,9 @@ public Address() {} // With usingJacksonDeserializerAnnotation=false, NO @JsonPOJOBuilder annotation should be used ProcessorAsserts.assertNotContaining(generatedCode, "@JsonPOJOBuilder"); + // With generateJavaDoc=false, NO Javadoc comments should be generated + ProcessorAsserts.assertNotContaining(generatedCode, "/**"); + // With builderAccess=PACKAGE_PRIVATE, builder class should NOT have public modifier ProcessorAsserts.assertNotContaining(generatedCode, "public class MinimalDtoCustomBuilder"); @@ -346,6 +353,146 @@ public Address() {} "static MinimalDtoCustomBuilder create()"); } + /** + * Inline annotation test: Disabling Javadoc via {@code @SimpleBuilder.Options} removes Javadoc + * blocks while keeping the builder API intact. + */ + @Test + void inlineOptions_generateJavaDocDisabled_ShouldNotGenerateJavadoc() { + JavaFileObject source = + ProcessorTestUtils.forSource( + """ + package test; + + import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; + import org.javahelpers.simple.builders.core.enums.OptionState; + + @SimpleBuilder(options = @SimpleBuilder.Options(generateJavaDoc = OptionState.DISABLED)) + public class PersonDto { + private String name; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + } + """); + + Compilation compilation = ProcessorTestUtils.createCompiler().compile(source); + + assertThat(compilation).succeeded(); + + String generatedCode = ProcessorTestUtils.loadGeneratedSource(compilation, "PersonDtoBuilder"); + + String expectedCode = + """ + package test; + + 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.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; + + @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor") + @BuilderImplementation(forClass = PersonDto.class) + public class PersonDtoBuilder implements IBuilderBase { + + private TrackedValue name = unsetValue(); + + public PersonDtoBuilder() { + } + + public PersonDtoBuilder(PersonDto instance) { + this.name = initialValue(instance.getName()); + } + + public static PersonDtoBuilder create() { + return new PersonDtoBuilder(); + } + + public PersonDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + + public PersonDtoBuilder name(Consumer nameStringBuilderConsumer) { + StringBuilder builder = new StringBuilder(); + nameStringBuilderConsumer.accept(builder); + this.name = changedValue(builder.toString()); + return this; + } + + public PersonDtoBuilder name(Supplier nameSupplier) { + this.name = changedValue(nameSupplier.get()); + return this; + } + + public PersonDtoBuilder name(String format, Object... args) { + this.name = changedValue(String.format(format, args)); + return this; + } + + public PersonDtoBuilder conditional(BooleanSupplier condition, Consumer yesCondition) { + return conditional(condition, yesCondition, null); + } + + public PersonDtoBuilder conditional(BooleanSupplier condition, Consumer trueCase, + Consumer falseCase) { + if (condition.getAsBoolean()) { + trueCase.accept(this); + } else if (falseCase != null) { + falseCase.accept(this); + } + return this; + } + + @Override + public PersonDto build() { + PersonDto result = new PersonDto(); + this.name.ifSet(result::setName); + return result; + } + + @Override + public String toString() { + return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE).append("name", this.name).toString(); + } + + public interface With { + default PersonDto with(Consumer b) { + PersonDtoBuilder builder; + try { + builder = new PersonDtoBuilder(PersonDto.class.cast(this)); + } catch (ClassCastException ex) { + throw new IllegalArgumentException( + "The interface 'PersonDtoBuilder.With' should only be implemented by classes, which could be casted to 'PersonDto'", + ex); + } + b.accept(builder); + return builder.build(); + } + + default PersonDtoBuilder with() { + try { + return new PersonDtoBuilder(PersonDto.class.cast(this)); + } catch (ClassCastException ex) { + throw new IllegalArgumentException( + "The interface 'PersonDtoBuilder.With' should only be implemented by classes, which could be casted to 'PersonDto'", + ex); + } + } + } + }"""; + + assertEquals(expectedCode, generatedCode); + } + /** * Merge logic test: Configuration merging must respect priority correctly. * diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/SimpleMinimalBuilderTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/SimpleMinimalBuilderTest.java index c81d9e4f..963846c9 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/SimpleMinimalBuilderTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/SimpleMinimalBuilderTest.java @@ -84,108 +84,34 @@ public class PersonDto { import org.javahelpers.simple.builders.core.util.BuilderToStringStyle; import org.javahelpers.simple.builders.core.util.TrackedValue; - /** - * Builder for {@code test.PersonDto}. - *

    - * This builder provides a fluent API for creating instances of test.PersonDto 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. - * - *

    Example:

    - * - *
    {@code
    -         * PersonDto result = PersonDtoBuilder.create().name("example value").tags(List.of("example value")).build();
    -         * }
    - */ public class PersonDtoBuilder { - /** - * Tracked value for name: name. - */ private TrackedValue name = unsetValue(); - /** - * Tracked value for tags: tags. - */ + private TrackedValue> tags = unsetValue(); - /** - * Empty constructor of builder for {@code test.PersonDto}. - */ public PersonDtoBuilder() { } - /** - * Initialisation of builder for {@code test.PersonDto} by a instance. - * - * @param instance object instance for initialisiation - */ public PersonDtoBuilder(PersonDto instance) { this.name = initialValue(instance.getName()); this.tags = initialValue(instance.getTags()); } - /** - * Creating a new builder for {@code test.PersonDto}. - * - *

    Example:

    - * - *
    {@code
    -           * PersonDtoBuilder builder = PersonDtoBuilder.create();
    -           * }
    - * - * @return builder for {@code test.PersonDto} - */ public static PersonDtoBuilder create() { return new PersonDtoBuilder(); } - /** - * Sets the value for name. - *

    - * Generated from setter {@link PersonDto#setName(String) setName(String name)} - * - *

    Example:

    - * - *
    {@code
    -           * builder.name("example value");
    -           * }
    - * - * @param name name - * @return current instance of builder - */ public PersonDtoBuilder name(String name) { this.name = changedValue(name); return this; } - /** - * Sets the value for tags. - *

    - * Generated from setter {@link PersonDto#setTags(List) setTags(List tags)} - * - *

    Example:

    - * - *
    {@code
    -           * builder.tags(List.of("example value"));
    -           * }
    - * - * @param tags tags - * @return current instance of builder - */ public PersonDtoBuilder tags(List tags) { this.tags = changedValue(tags); return this; } - /** - * Builds the configured DTO instance. - * - *

    Example:

    - * - *
    {@code
    -           * PersonDto result = builder.build();
    -           * }
    - */ public PersonDto build() { PersonDto result = new PersonDto(); this.name.ifSet(result::setName); @@ -193,11 +119,6 @@ public PersonDto build() { 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)