diff --git a/README.md b/README.md index bd3aec0f..dff0a901 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Value semantics (`equals`, `hashCode`, `toString`) and generating brand-new immu - **Annotation Preservation**: Validation annotations are automatically copied to builder methods - **With Interface Pattern**: Type-safe object modifications using generated With interfaces - **Jackson Support**: Supporting Jackson deserialization via `@JsonPOJOBuilder` and optional generation of `SimpleModule`s (one per package) (both need to be enabled) +- **JavaDoc Usage Examples**: Generated builder methods include auto-generated usage examples in their JavaDoc (per-method fluent snippets plus a class-level example), so IDE tooltips show exactly how to use each builder ## Requirements diff --git a/example-custom-generator/src/main/java/org/javahelpers/simple/builders/example/custom/StringValidationGenerator.java b/example-custom-generator/src/main/java/org/javahelpers/simple/builders/example/custom/StringValidationGenerator.java index ef197823..054780ce 100644 --- a/example-custom-generator/src/main/java/org/javahelpers/simple/builders/example/custom/StringValidationGenerator.java +++ b/example-custom-generator/src/main/java/org/javahelpers/simple/builders/example/custom/StringValidationGenerator.java @@ -27,7 +27,7 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; import org.javahelpers.simple.builders.processor.generators.MethodGenerator; @@ -66,7 +66,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { String fieldInDto = field.getOriginalFieldName(); String fieldInBuilder = field.getFieldNameInBuilder(); String methodName = "validate" + StringUtils.capitalize(fieldInDto); @@ -79,7 +79,7 @@ public List generateMethods(FieldDto field, TypeName builderType, Pro fieldInBuilder, fieldInBuilder, StringUtils.capitalize(fieldInDto) ); - MethodDto validationMethod = new MethodDto(methodName, builderType); + BuilderMethodDto validationMethod = new BuilderMethodDto(methodName, builderType); validationMethod.setCode(methodBody); validationMethod.setJavadoc( new JavadocDto("Validates that the " + fieldInDto + " field is not null or empty.") diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/BookDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/BookDtoBuilder.java index 0461b5df..3de32abf 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/BookDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/BookDtoBuilder.java @@ -20,6 +20,30 @@ * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.BookDto 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
+ * BookDto result = BookDtoBuilder.create()
+ *     .author("example value")
+ *     .available(true)
+ *     .category('x')
+ *     .discount(3.14f)
+ *     .exactPrice(BigDecimal.valueOf(3.14))
+ *     .genres(Set.of("example value"))
+ *     .isbn("example value")
+ *     .lastUpdated(LocalDateTime.now())
+ *     .metadata(Map.of("example value", "example value"))
+ *     .pages(42)
+ *     .price(3.14)
+ *     .publishDate(LocalDate.now())
+ *     .publisher(PersonDtoBuilder.create().build())
+ *     .salesCount(42L)
+ *     .subtitle(Optional.of("example value"))
+ *     .tags(List.of("example value"))
+ *     .title("example value")
+ *     .build();
+ * }
*/ public class BookDtoBuilder { @@ -136,6 +160,12 @@ public BookDtoBuilder(BookDto instance) { /** * Creating a new builder for {@code org.javahelpers.simple.builders.example.BookDto}. * + *

Example:

+ * + *
{@code
+   * BookDtoBuilder builder = BookDtoBuilder.create();
+   * }
+ * * @return builder for {@code org.javahelpers.simple.builders.example.BookDto} */ public static BookDtoBuilder create() { @@ -144,6 +174,14 @@ public static BookDtoBuilder create() { /** * Sets the value for author. + *

+ * Generated from setter {@link BookDto#setAuthor(String) setAuthor(String author)} + * + *

Example:

+ * + *
{@code
+   * builder.author("example value");
+   * }
* * @param author the book author to set * @return current instance of builder @@ -155,6 +193,14 @@ public BookDtoBuilder author(String author) { /** * Sets the value for available. + *

+ * Generated from setter {@link BookDto#setAvailable(boolean) setAvailable(boolean available)} + * + *

Example:

+ * + *
{@code
+   * builder.available(true);
+   * }
* * @param available true if available, false otherwise * @return current instance of builder @@ -166,6 +212,14 @@ public BookDtoBuilder available(boolean available) { /** * Sets the value for category. + *

+ * Generated from setter {@link BookDto#setCategory(char) setCategory(char category)} + * + *

Example:

+ * + *
{@code
+   * builder.category('x');
+   * }
* * @param category the category code to set * @return current instance of builder @@ -177,6 +231,14 @@ public BookDtoBuilder category(char category) { /** * Sets the value for discount. + *

+ * Generated from setter {@link BookDto#setDiscount(float) setDiscount(float discount)} + * + *

Example:

+ * + *
{@code
+   * builder.discount(3.14f);
+   * }
* * @param discount the discount percentage to set * @return current instance of builder @@ -188,6 +250,8 @@ public BookDtoBuilder discount(float discount) { /** * Sets the value for edition. + *

+ * Generated from setter {@link BookDto#setEdition(short) setEdition(short edition)} * * @param edition the edition number to set * @return current instance of builder @@ -199,6 +263,14 @@ public BookDtoBuilder edition(short edition) { /** * Sets the value for exactPrice. + *

+ * Generated from setter {@link BookDto#setExactPrice(BigDecimal) setExactPrice(BigDecimal exactPrice)} + * + *

Example:

+ * + *
{@code
+   * builder.exactPrice(BigDecimal.valueOf(3.14));
+   * }
* * @param exactPrice the exact book price to set * @return current instance of builder @@ -210,6 +282,14 @@ public BookDtoBuilder exactPrice(BigDecimal exactPrice) { /** * Sets the value for genres. + *

+ * Generated from setter {@link BookDto#setGenres(Set) setGenres(Set genres)} + * + *

Example:

+ * + *
{@code
+   * builder.genres(Set.of("example value"));
+   * }
* * @param genres the set of genres to set * @return current instance of builder @@ -221,6 +301,14 @@ public BookDtoBuilder genres(Set genres) { /** * Sets the value for isbn. + *

+ * Generated from setter {@link BookDto#setIsbn(String) setIsbn(String isbn)} + * + *

Example:

+ * + *
{@code
+   * builder.isbn("example value");
+   * }
* * @param isbn the ISBN to set * @return current instance of builder @@ -232,6 +320,14 @@ public BookDtoBuilder isbn(String isbn) { /** * Sets the value for lastUpdated. + *

+ * Generated from setter {@link BookDto#setLastUpdated(LocalDateTime) setLastUpdated(LocalDateTime lastUpdated)} + * + *

Example:

+ * + *
{@code
+   * builder.lastUpdated(LocalDateTime.now());
+   * }
* * @param lastUpdated the last update timestamp to set * @return current instance of builder @@ -243,6 +339,14 @@ public BookDtoBuilder lastUpdated(LocalDateTime lastUpdated) { /** * Sets the value for metadata. + *

+ * Generated from setter {@link BookDto#setMetadata(Map) setMetadata(Map metadata)} + * + *

Example:

+ * + *
{@code
+   * builder.metadata(Map.of("example value", "example value"));
+   * }
* * @param metadata the metadata map to set * @return current instance of builder @@ -254,6 +358,14 @@ public BookDtoBuilder metadata(Map metadata) { /** * Sets the value for pages. + *

+ * Generated from setter {@link BookDto#setPages(int) setPages(int pages)} + * + *

Example:

+ * + *
{@code
+   * builder.pages(42);
+   * }
* * @param pages the page count to set * @return current instance of builder @@ -265,6 +377,14 @@ public BookDtoBuilder pages(int pages) { /** * Sets the value for price. + *

+ * Generated from setter {@link BookDto#setPrice(double) setPrice(double price)} + * + *

Example:

+ * + *
{@code
+   * builder.price(3.14);
+   * }
* * @param price the book price to set * @return current instance of builder @@ -276,6 +396,14 @@ public BookDtoBuilder price(double price) { /** * Sets the value for publishDate. + *

+ * Generated from setter {@link BookDto#setPublishDate(LocalDate) setPublishDate(LocalDate publishDate)} + * + *

Example:

+ * + *
{@code
+   * builder.publishDate(LocalDate.now());
+   * }
* * @param publishDate the publication date to set * @return current instance of builder @@ -287,6 +415,14 @@ public BookDtoBuilder publishDate(LocalDate publishDate) { /** * Sets the value for publisher. + *

+ * Generated from setter {@link BookDto#setPublisher(PersonDto) setPublisher(PersonDto publisher)} + * + *

Example:

+ * + *
{@code
+   * builder.publisher(PersonDtoBuilder.create().build());
+   * }
* * @param publisher the publisher to set * @return current instance of builder @@ -298,6 +434,8 @@ public BookDtoBuilder publisher(PersonDto publisher) { /** * Sets the value for rating. + *

+ * Generated from setter {@link BookDto#setRating(byte) setRating(byte rating)} * * @param rating the book rating to set * @return current instance of builder @@ -309,6 +447,14 @@ public BookDtoBuilder rating(byte rating) { /** * Sets the value for salesCount. + *

+ * Generated from setter {@link BookDto#setSalesCount(long) setSalesCount(long salesCount)} + * + *

Example:

+ * + *
{@code
+   * builder.salesCount(42L);
+   * }
* * @param salesCount the sales count to set * @return current instance of builder @@ -320,6 +466,14 @@ public BookDtoBuilder salesCount(long salesCount) { /** * Sets the value for subtitle. + *

+ * Generated from setter {@link BookDto#setSubtitle(Optional) setSubtitle(Optional subtitle)} + * + *

Example:

+ * + *
{@code
+   * builder.subtitle(Optional.of("example value"));
+   * }
* * @param subtitle an Optional containing the subtitle to set * @return current instance of builder @@ -331,6 +485,14 @@ public BookDtoBuilder subtitle(Optional subtitle) { /** * Sets the value for tags. + *

+ * Generated from setter {@link BookDto#setTags(List) setTags(List tags)} + * + *

Example:

+ * + *
{@code
+   * builder.tags(List.of("example value"));
+   * }
* * @param tags the list of tags to set * @return current instance of builder @@ -342,6 +504,14 @@ public BookDtoBuilder tags(List tags) { /** * Sets the value for title. + *

+ * Generated from setter {@link BookDto#setTitle(String) setTitle(String title)} + * + *

Example:

+ * + *
{@code
+   * builder.title("example value");
+   * }
* * @param title the book title to set * @return current instance of builder @@ -353,6 +523,8 @@ public BookDtoBuilder title(String title) { /** * Validates that the author field is not null or empty. + *

+ * Generated from setter {@link BookDto#setAuthor(String) setAuthor(String author)} * * @return this builder instance for chaining * @throws IllegalArgumentException if author is null or empty @@ -366,6 +538,8 @@ BookDtoBuilder validateAuthor() { /** * Validates that the isbn field is not null or empty. + *

+ * Generated from setter {@link BookDto#setIsbn(String) setIsbn(String isbn)} * * @return this builder instance for chaining * @throws IllegalArgumentException if isbn is null or empty @@ -379,6 +553,8 @@ BookDtoBuilder validateIsbn() { /** * Validates that the title field is not null or empty. + *

+ * Generated from setter {@link BookDto#setTitle(String) setTitle(String title)} * * @return this builder instance for chaining * @throws IllegalArgumentException if title is null or empty @@ -392,6 +568,12 @@ BookDtoBuilder validateTitle() { /** * Builds the configured DTO instance. + * + *

Example:

+ * + *
{@code
+   * BookDto result = builder.build();
+   * }
*/ public BookDto build() { if (this.available.isSet() && this.available.value() == null) { diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java index b9aba88d..199010b5 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java @@ -21,6 +21,19 @@ * org.javahelpers.simple.builders.example.JacksonIntegrationDto 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
+ * JacksonIntegrationDto result = JacksonIntegrationDtoBuilder.create()
+ *     .name("example value")
+ *     .name("Hello %s", "World")
+ *     .name(() -> "example value")
+ *     .name(sb -> sb.append("text"))
+ *     .age(42)
+ *     .age(() -> 42)
+ *     .build();
+ * }
*/ @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor") @BuilderImplementation(forClass = JacksonIntegrationDto.class) @@ -55,6 +68,12 @@ public JacksonIntegrationDtoBuilder(JacksonIntegrationDto instance) { /** * Creating a new builder for {@code org.javahelpers.simple.builders.example.JacksonIntegrationDto}. * + *

Example:

+ * + *
{@code
+   * JacksonIntegrationDtoBuilder builder = JacksonIntegrationDtoBuilder.create();
+   * }
+ * * @return builder for {@code org.javahelpers.simple.builders.example.JacksonIntegrationDto} */ public static JacksonIntegrationDtoBuilder create() { @@ -63,6 +82,15 @@ public static JacksonIntegrationDtoBuilder create() { /** * Sets the value for age. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} + * + *

Example:

+ * + *
{@code
+   * builder.age(42);
+   * }
* * @param age age * @return current instance of builder @@ -74,6 +102,15 @@ public JacksonIntegrationDtoBuilder age(int age) { /** * Sets the value for age by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} + * + *

Example:

+ * + *
{@code
+   * builder.age(() -> 42);
+   * }
* * @param ageSupplier supplier for age * @return current instance of builder @@ -85,6 +122,15 @@ public JacksonIntegrationDtoBuilder age(Supplier ageSupplier) { /** * Sets the value for name. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
* * @param name name * @return current instance of builder @@ -96,6 +142,15 @@ public JacksonIntegrationDtoBuilder name(String name) { /** * Sets the value for name by executing the provided consumer. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} + * + *

Example:

+ * + *
{@code
+   * builder.name(sb -> sb.append("text"));
+   * }
* * @param nameStringBuilderConsumer consumer providing an instance of name * @return current instance of builder @@ -109,6 +164,15 @@ public JacksonIntegrationDtoBuilder name(Consumer nameStringBuild /** * Sets the value for name by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} + * + *

Example:

+ * + *
{@code
+   * builder.name(() -> "example value");
+   * }
* * @param nameSupplier supplier for name * @return current instance of builder @@ -121,6 +185,15 @@ public JacksonIntegrationDtoBuilder name(Supplier nameSupplier) { /** * Sets the String value for name by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} + * + *

Example:

+ * + *
{@code
+   * builder.name("Hello %s", "World");
+   * }
* * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -133,6 +206,9 @@ public JacksonIntegrationDtoBuilder name(String format, Object... args) { /** * Validates that the name field is not null or empty. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} * * @return this builder instance for chaining * @throws IllegalArgumentException if name is null or empty @@ -176,6 +252,12 @@ public JacksonIntegrationDtoBuilder conditional(BooleanSupplier condition, /** * Builds the configured DTO instance. + * + *

Example:

+ * + *
{@code
+   * JacksonIntegrationDto result = builder.build();
+   * }
*/ @Override public JacksonIntegrationDto build() { diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java index 1ad4b485..4333788d 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java @@ -22,6 +22,18 @@ * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.MannschaftDto * 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
+ * MannschaftDto result = MannschaftDtoBuilder.create()
+ *     .name("example value")
+ *     .name("Hello %s", "World")
+ *     .name(() -> "example value")
+ *     .name(sb -> sb.append("text"))
+ *     .sponsoren(t -> t.add(sponsorDtoBuilder -> sponsorDtoBuilder))
+ *     .build();
+ * }
*/ @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor") @BuilderImplementation(forClass = MannschaftDto.class) @@ -55,6 +67,12 @@ public MannschaftDtoBuilder(MannschaftDto instance) { /** * Creating a new builder for {@code org.javahelpers.simple.builders.example.MannschaftDto}. * + *

Example:

+ * + *
{@code
+   * MannschaftDtoBuilder builder = MannschaftDtoBuilder.create();
+   * }
+ * * @return builder for {@code org.javahelpers.simple.builders.example.MannschaftDto} */ public static MannschaftDtoBuilder create() { @@ -63,6 +81,8 @@ public static MannschaftDtoBuilder create() { /** * Adds a single element to sponsoren. + *

+ * Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} * * @param element the element to add * @return current instance of builder @@ -81,6 +101,14 @@ public MannschaftDtoBuilder add2Sponsoren(SponsorDto element) { /** * Sets the value for name. + *

+ * Generated from setter {@link MannschaftDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
* * @param name name * @return current instance of builder @@ -92,6 +120,14 @@ public MannschaftDtoBuilder name(String name) { /** * Sets the value for name by executing the provided consumer. + *

+ * Generated from setter {@link MannschaftDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name(sb -> sb.append("text"));
+   * }
* * @param nameStringBuilderConsumer consumer providing an instance of name * @return current instance of builder @@ -105,6 +141,14 @@ public MannschaftDtoBuilder name(Consumer nameStringBuilderConsum /** * Sets the value for name by invoking the provided supplier. + *

+ * Generated from setter {@link MannschaftDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name(() -> "example value");
+   * }
* * @param nameSupplier supplier for name * @return current instance of builder @@ -117,6 +161,14 @@ public MannschaftDtoBuilder name(Supplier nameSupplier) { /** * Sets the String value for name by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

+ * Generated from setter {@link MannschaftDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("Hello %s", "World");
+   * }
* * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -129,6 +181,8 @@ public MannschaftDtoBuilder name(String format, Object... args) { /** * Sets the value for sponsoren. + *

+ * Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} * * @param sponsoren sponsoren * @return current instance of builder @@ -140,6 +194,8 @@ public MannschaftDtoBuilder sponsoren(SponsorDto... sponsoren) { /** * Sets the value for sponsoren. + *

+ * Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} * * @param sponsoren sponsoren * @return current instance of builder @@ -151,6 +207,14 @@ public MannschaftDtoBuilder sponsoren(Set sponsoren) { /** * Sets the value for sponsoren using a builder consumer that produces the value. + *

+ * Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} + * + *

Example:

+ * + *
{@code
+   * builder.sponsoren(t -> t.add(sponsorDtoBuilder -> sponsorDtoBuilder));
+   * }
* * @param sponsorenBuilderConsumer consumer providing an instance of a builder for sponsoren * @return current instance of builder @@ -168,6 +232,8 @@ public MannschaftDtoBuilder sponsoren( /** * Sets the value for sponsoren by invoking the provided supplier. + *

+ * Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} * * @param sponsorenSupplier supplier for sponsoren * @return current instance of builder @@ -179,6 +245,8 @@ public MannschaftDtoBuilder sponsoren(Supplier> sponsorenSupplie /** * Validates that the name field is not null or empty. + *

+ * Generated from setter {@link MannschaftDto#setName(String) setName(String name)} * * @return this builder instance for chaining * @throws IllegalArgumentException if name is null or empty @@ -221,6 +289,12 @@ public MannschaftDtoBuilder conditional(BooleanSupplier condition, ConsumerExample: + * + *

{@code
+   * MannschaftDto result = builder.build();
+   * }
*/ @Override public MannschaftDto build() { diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java index c3f6e7a4..fd94d1d0 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java @@ -23,6 +23,30 @@ * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.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")
+ *     .name("Hello %s", "World")
+ *     .name(() -> "example value")
+ *     .name(sb -> sb.append("text"))
+ *     .birthdate(LocalDate.now())
+ *     .birthdate(() -> LocalDate.now())
+ *     .mannschaft(new MannschaftDto())
+ *     .mannschaft(MannschaftDto::new)
+ *     .mannschaft(mannschaftDtoBuilder -> mannschaftDtoBuilder)
+ *     .nickNames(List.of("example value"))
+ *     .nickNames(() -> List.of("example value"))
+ *     .nickNames(t -> t.add("example value"))
+ *     .nickNames("example value", "example value")
+ *     .add2NickNames("example value")
+ *     .nickNames2("example value")
+ *     .nickNames2(() -> "example value")
+ *     .nickNames2("example value")
+ *     .build();
+ * }
*/ @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor") @BuilderImplementation(forClass = PersonDto.class) @@ -70,6 +94,12 @@ public PersonDtoBuilder(PersonDto instance) { /** * Creating a new builder for {@code org.javahelpers.simple.builders.example.PersonDto}. * + *

Example:

+ * + *
{@code
+   * PersonDtoBuilder builder = PersonDtoBuilder.create();
+   * }
+ * * @return builder for {@code org.javahelpers.simple.builders.example.PersonDto} */ public static PersonDtoBuilder create() { @@ -78,6 +108,14 @@ public static PersonDtoBuilder create() { /** * Adds a single element to nickNames. + *

+ * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} + * + *

Example:

+ * + *
{@code
+   * builder.add2NickNames("example value");
+   * }
* * @param element the element to add * @return current instance of builder @@ -96,6 +134,14 @@ public PersonDtoBuilder add2NickNames(String element) { /** * Sets the value for birthdate. + *

+ * Generated from setter {@link PersonDto#setBirthdate(LocalDate) setBirthdate(LocalDate birthdate)} + * + *

Example:

+ * + *
{@code
+   * builder.birthdate(LocalDate.now());
+   * }
* * @param birthdate birthdate * @return current instance of builder @@ -107,6 +153,14 @@ public PersonDtoBuilder birthdate(LocalDate birthdate) { /** * Sets the value for birthdate by invoking the provided supplier. + *

+ * Generated from setter {@link PersonDto#setBirthdate(LocalDate) setBirthdate(LocalDate birthdate)} + * + *

Example:

+ * + *
{@code
+   * builder.birthdate(() -> LocalDate.now());
+   * }
* * @param birthdateSupplier supplier for birthdate * @return current instance of builder @@ -118,6 +172,14 @@ public PersonDtoBuilder birthdate(Supplier birthdateSupplier) { /** * Sets the value for mannschaft. + *

+ * Generated from setter {@link PersonDto#setMannschaft(MannschaftDto) setMannschaft(MannschaftDto mannschaft)} + * + *

Example:

+ * + *
{@code
+   * builder.mannschaft(new MannschaftDto());
+   * }
* * @param mannschaft mannschaft * @return current instance of builder @@ -129,6 +191,14 @@ public PersonDtoBuilder mannschaft(MannschaftDto mannschaft) { /** * Sets the value for mannschaft using a builder consumer that produces the value. + *

+ * Generated from setter {@link PersonDto#setMannschaft(MannschaftDto) setMannschaft(MannschaftDto mannschaft)} + * + *

Example:

+ * + *
{@code
+   * builder.mannschaft(mannschaftDtoBuilder -> mannschaftDtoBuilder);
+   * }
* * @param mannschaftBuilderConsumer consumer providing an instance of a builder for mannschaft * @return current instance of builder @@ -144,6 +214,14 @@ public PersonDtoBuilder mannschaft(Consumer mannschaftBuil /** * Sets the value for mannschaft by invoking the provided supplier. + *

+ * Generated from setter {@link PersonDto#setMannschaft(MannschaftDto) setMannschaft(MannschaftDto mannschaft)} + * + *

Example:

+ * + *
{@code
+   * builder.mannschaft(MannschaftDto::new);
+   * }
* * @param mannschaftSupplier supplier for mannschaft * @return current instance of builder @@ -155,6 +233,14 @@ public PersonDtoBuilder mannschaft(Supplier mannschaftSupplier) { /** * Sets the value for name. + *

+ * Generated from parameter in constructor {@link PersonDto#PersonDto(String) PersonDto(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
* * @param name name * @return current instance of builder @@ -166,6 +252,14 @@ public PersonDtoBuilder name(String name) { /** * Sets the value for name by executing the provided consumer. + *

+ * Generated from parameter in constructor {@link PersonDto#PersonDto(String) PersonDto(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name(sb -> sb.append("text"));
+   * }
* * @param nameStringBuilderConsumer consumer providing an instance of name * @return current instance of builder @@ -179,6 +273,14 @@ public PersonDtoBuilder name(Consumer nameStringBuilderConsumer) /** * Sets the value for name by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link PersonDto#PersonDto(String) PersonDto(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name(() -> "example value");
+   * }
* * @param nameSupplier supplier for name * @return current instance of builder @@ -191,6 +293,14 @@ public PersonDtoBuilder name(Supplier nameSupplier) { /** * Sets the String value for name by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

+ * Generated from parameter in constructor {@link PersonDto#PersonDto(String) PersonDto(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("Hello %s", "World");
+   * }
* * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -203,6 +313,14 @@ public PersonDtoBuilder name(String format, Object... args) { /** * Sets the value for nickNames. + *

+ * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames("example value", "example value");
+   * }
* * @param nickNames nickNames * @return current instance of builder @@ -214,6 +332,14 @@ public PersonDtoBuilder nickNames(String... nickNames) { /** * Sets the value for nickNames. + *

+ * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames(List.of("example value"));
+   * }
* * @param nickNames nickNames * @return current instance of builder @@ -225,6 +351,14 @@ public PersonDtoBuilder nickNames(List nickNames) { /** * Sets the value for nickNames using a builder consumer that produces the value. + *

+ * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames(t -> t.add("example value"));
+   * }
* * @param nickNamesBuilderConsumer consumer providing an instance of a builder for nickNames * @return current instance of builder @@ -240,6 +374,14 @@ public PersonDtoBuilder nickNames(Consumer> nickNamesBu /** * Sets the value for nickNames by invoking the provided supplier. + *

+ * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames(() -> List.of("example value"));
+   * }
* * @param nickNamesSupplier supplier for nickNames * @return current instance of builder @@ -251,6 +393,14 @@ public PersonDtoBuilder nickNames(Supplier> nickNamesSupplier) { /** * Sets the value for nickNames2. + *

+ * Generated from setter {@link PersonDto#setNickNames2(String) setNickNames2(String nickNames2)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames2("example value");
+   * }
* * @param nickNames2 nickNames2 * @return current instance of builder @@ -262,6 +412,14 @@ public PersonDtoBuilder nickNames2(String... nickNames2) { /** * Sets the value for nickNames2. + *

+ * Generated from setter {@link PersonDto#setNickNames2(String) setNickNames2(String nickNames2)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames2("example value");
+   * }
* * @param nickNames2 nickNames2 * @return current instance of builder @@ -273,6 +431,8 @@ public PersonDtoBuilder nickNames2(List nickNames2) { /** * Sets the value for nickNames2 using the fluent builder consumer. + *

+ * Generated from setter {@link PersonDto#setNickNames2(String) setNickNames2(String nickNames2)} * * @param nickNames2BuilderConsumer consumer for nickNames2 * @return current instance of builder @@ -288,6 +448,14 @@ public PersonDtoBuilder nickNames2(Consumer> nickNames2 /** * Sets the value for nickNames2 by invoking the provided supplier. + *

+ * Generated from setter {@link PersonDto#setNickNames2(String) setNickNames2(String nickNames2)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames2(() -> "example value");
+   * }
* * @param nickNames2Supplier supplier for nickNames2 * @return current instance of builder @@ -299,6 +467,8 @@ public PersonDtoBuilder nickNames2(Supplier nickNames2Supplier) { /** * Validates that the name field is not null or empty. + *

+ * Generated from parameter in constructor {@link PersonDto#PersonDto(String) PersonDto(String name)} * * @return this builder instance for chaining * @throws IllegalArgumentException if name is null or empty @@ -341,6 +511,12 @@ public PersonDtoBuilder conditional(BooleanSupplier condition, ConsumerExample: + * + *

{@code
+   * PersonDto result = builder.build();
+   * }
*/ @Override public PersonDto build() { diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java index adc17e48..2d753258 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java @@ -19,6 +19,23 @@ * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.ProductRecord * 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
+ * ProductRecord result = ProductRecordBuilder.create()
+ *     .name("example value")
+ *     .name("Hello %s", "World")
+ *     .name(() -> "example value")
+ *     .name(sb -> sb.append("text"))
+ *     .price(3.14)
+ *     .price(() -> 3.14)
+ *     .category("example value")
+ *     .category("Hello %s", "World")
+ *     .category(() -> "example value")
+ *     .category(sb -> sb.append("text"))
+ *     .build();
+ * }
*/ @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor") @BuilderImplementation(forClass = ProductRecord.class) @@ -57,6 +74,12 @@ public ProductRecordBuilder(ProductRecord instance) { /** * Creating a new builder for {@code org.javahelpers.simple.builders.example.ProductRecord}. * + *

Example:

+ * + *
{@code
+   * ProductRecordBuilder builder = ProductRecordBuilder.create();
+   * }
+ * * @return builder for {@code org.javahelpers.simple.builders.example.ProductRecord} */ public static ProductRecordBuilder create() { @@ -65,6 +88,15 @@ public static ProductRecordBuilder create() { /** * Sets the value for category. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.category("example value");
+   * }
* * @param category category * @return current instance of builder @@ -76,6 +108,15 @@ public ProductRecordBuilder category(String category) { /** * Sets the value for category by executing the provided consumer. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.category(sb -> sb.append("text"));
+   * }
* * @param categoryStringBuilderConsumer consumer providing an instance of category * @return current instance of builder @@ -89,6 +130,15 @@ public ProductRecordBuilder category(Consumer categoryStringBuild /** * Sets the value for category by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.category(() -> "example value");
+   * }
* * @param categorySupplier supplier for category * @return current instance of builder @@ -101,6 +151,15 @@ public ProductRecordBuilder category(Supplier categorySupplier) { /** * Sets the String value for category by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.category("Hello %s", "World");
+   * }
* * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -113,6 +172,15 @@ public ProductRecordBuilder category(String format, Object... args) { /** * Sets the value for name. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
* * @param name name * @return current instance of builder @@ -124,6 +192,15 @@ public ProductRecordBuilder name(String name) { /** * Sets the value for name by executing the provided consumer. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.name(sb -> sb.append("text"));
+   * }
* * @param nameStringBuilderConsumer consumer providing an instance of name * @return current instance of builder @@ -137,6 +214,15 @@ public ProductRecordBuilder name(Consumer nameStringBuilderConsum /** * Sets the value for name by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.name(() -> "example value");
+   * }
* * @param nameSupplier supplier for name * @return current instance of builder @@ -149,6 +235,15 @@ public ProductRecordBuilder name(Supplier nameSupplier) { /** * Sets the String value for name by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.name("Hello %s", "World");
+   * }
* * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -161,6 +256,15 @@ public ProductRecordBuilder name(String format, Object... args) { /** * Sets the value for price. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.price(3.14);
+   * }
* * @param price price * @return current instance of builder @@ -172,6 +276,15 @@ public ProductRecordBuilder price(double price) { /** * Sets the value for price by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.price(() -> 3.14);
+   * }
* * @param priceSupplier supplier for price * @return current instance of builder @@ -183,6 +296,9 @@ public ProductRecordBuilder price(Supplier priceSupplier) { /** * Validates that the category field is not null or empty. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} * * @return this builder instance for chaining * @throws IllegalArgumentException if category is null or empty @@ -196,6 +312,9 @@ ProductRecordBuilder validateCategory() { /** * Validates that the name field is not null or empty. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} * * @return this builder instance for chaining * @throws IllegalArgumentException if name is null or empty @@ -238,6 +357,12 @@ public ProductRecordBuilder conditional(BooleanSupplier condition, ConsumerExample: + * + *

{@code
+   * ProductRecord result = builder.build();
+   * }
*/ @Override public ProductRecord build() { diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java index 295bcb70..f9d6982b 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java @@ -19,6 +19,17 @@ * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.SponsorDto 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
+ * SponsorDto result = SponsorDtoBuilder.create()
+ *     .name("example value")
+ *     .name("Hello %s", "World")
+ *     .name(() -> "example value")
+ *     .name(sb -> sb.append("text"))
+ *     .build();
+ * }
*/ @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor") @BuilderImplementation(forClass = SponsorDto.class) @@ -47,6 +58,12 @@ public SponsorDtoBuilder(SponsorDto instance) { /** * Creating a new builder for {@code org.javahelpers.simple.builders.example.SponsorDto}. * + *

Example:

+ * + *
{@code
+   * SponsorDtoBuilder builder = SponsorDtoBuilder.create();
+   * }
+ * * @return builder for {@code org.javahelpers.simple.builders.example.SponsorDto} */ public static SponsorDtoBuilder create() { @@ -55,6 +72,14 @@ public static SponsorDtoBuilder create() { /** * Sets the value for name. + *

+ * Generated from setter {@link SponsorDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
* * @param name name * @return current instance of builder @@ -66,6 +91,14 @@ public SponsorDtoBuilder name(String name) { /** * Sets the value for name by executing the provided consumer. + *

+ * Generated from setter {@link SponsorDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name(sb -> sb.append("text"));
+   * }
* * @param nameStringBuilderConsumer consumer providing an instance of name * @return current instance of builder @@ -79,6 +112,14 @@ public SponsorDtoBuilder name(Consumer nameStringBuilderConsumer) /** * Sets the value for name by invoking the provided supplier. + *

+ * Generated from setter {@link SponsorDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name(() -> "example value");
+   * }
* * @param nameSupplier supplier for name * @return current instance of builder @@ -91,6 +132,14 @@ public SponsorDtoBuilder name(Supplier nameSupplier) { /** * Sets the String value for name by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

+ * Generated from setter {@link SponsorDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("Hello %s", "World");
+   * }
* * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -103,6 +152,8 @@ public SponsorDtoBuilder name(String format, Object... args) { /** * Validates that the name field is not null or empty. + *

+ * Generated from setter {@link SponsorDto#setName(String) setName(String name)} * * @return this builder instance for chaining * @throws IllegalArgumentException if name is null or empty @@ -145,6 +196,12 @@ public SponsorDtoBuilder conditional(BooleanSupplier condition, ConsumerExample: + * + *

{@code
+   * SponsorDto result = builder.build();
+   * }
*/ @Override public SponsorDto build() { 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 75bda27d..1c5786d0 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,6 +24,7 @@ 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 com.google.auto.service.AutoService; @@ -211,7 +212,8 @@ private void process(Element annotatedElement, BuilderConfiguration config) throws BuilderException { context.initConfigurationForProcessingTarget(config); BuilderDefinitionDto builderDef = extractFromElement(annotatedElement, context); - codeGenerator.generateClass(builderDef); + GenerationTargetClassDto renderingDto = toRenderingDto(builderDef); + codeGenerator.generateClass(renderingDto); // Collect info for Jackson Module if enabled jacksonModuleGenerator.addEntry(builderDef, annotatedElement); @@ -221,7 +223,7 @@ private void process(Element annotatedElement, BuilderConfiguration config) context.debugEndOperation( "Generated builder with %d fields and %d methods for %s", builderDef.getAllFieldsForBuilder().size(), - builderDef.getMethods().size(), + renderingDto.getMethods().size(), builderDef.getBuilderTypeName().getClassName()); } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java index 56a30ce6..1fa3b5c2 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java @@ -382,11 +382,11 @@ public static Optional findGetterForField( if (dtoType == null || fieldName == null || fieldTypeMirror == null) { return Optional.empty(); } - List classMethods = ElementFilter.methodsIn(context.getAllMembers(dtoType)); + List methods = ElementFilter.methodsIn(context.getAllMembers(dtoType)); // Check for accessor methods: // Record-style (fieldName), boolean-style (isXxx), or standard (getXxx) - for (ExecutableElement candidate : classMethods) { + for (ExecutableElement candidate : methods) { String name = candidate.getSimpleName().toString(); if (Strings.CI.equalsAny(name, fieldName, "is" + fieldName, "get" + fieldName) && candidate.getParameters().isEmpty() diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java index 959800e3..df6cc2f0 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java @@ -30,10 +30,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.Writer; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; @@ -46,6 +48,7 @@ import org.javahelpers.simple.builders.processor.model.core.ClassFieldDto; import org.javahelpers.simple.builders.processor.model.core.GenerationTargetClassDto; import org.javahelpers.simple.builders.processor.model.imports.ImportStatement; +import org.javahelpers.simple.builders.processor.model.javadoc.JavadocCodeBlockDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocTagDto; import org.javahelpers.simple.builders.processor.model.method.ConstructorDto; @@ -238,6 +241,19 @@ private void appendMethods(JavaClassSource source, GenerationTargetClassDto clas logger.debugEndOperation("Methods added: %d", resolvedMethods.size()); } + /** + * Generic safety net for method conflict resolution, preventing the code generator from producing + * invalid output (duplicate method signatures). + * + *

This is a generation-level check that operates on {@link MethodDto} (rendering-side DTO) + * which no longer carries field-origin metadata. Builder-specific conflict resolution with + * field-origin logging is performed earlier in {@link + * org.javahelpers.simple.builders.processor.processing.BuilderDefinitionCreator#resolveMethodConflicts}. + * + *

Since conflicts should already be resolved by the builder-specific step, this safety net + * simply keeps the first occurrence for any remaining duplicate signatures and logs a generic + * warning. + */ private List resolveMethodConflicts(List methods) { MethodDto.MethodComparator comparator = new MethodDto.MethodComparator(); @@ -253,20 +269,9 @@ private List resolveMethodConflicts(List methods) { if (existing == null) { signatureToMethod.put(signature, method); } else { - if (method.getPriority() > existing.getPriority()) { - signatureToMethod.put(signature, method); - logger.warning( - " Method conflict: '%s' (priority %d) dropped in favor of priority %d", - signature, existing.getPriority(), method.getPriority()); - } else if (method.getPriority() < existing.getPriority()) { - logger.warning( - " Method conflict: '%s' (priority %d) dropped in favor of priority %d", - signature, method.getPriority(), existing.getPriority()); - } else { - logger.warning( - " Method conflict: '%s' (priority %d) - equal priority, keeping first", - signature, method.getPriority()); - } + logger.warning( + " Unexpected duplicate method signature: '%s' — keeping first occurrence (safety net)", + signature); } } @@ -419,6 +424,27 @@ private void applyJavadoc( source.getJavaDoc().addTagValue(tag.getFullTagName(), ""); } } + + // Render the code example block + JavadocCodeBlockDto codeBlock = javadoc.getExampleUsageCodeBlock(); + if (codeBlock != null && codeBlock.hasCode()) { + // Resolve placeholders in the code block + String resolvedCode = resolveCodeTemplate(codeBlock); + // Pre-prefix every line of the code body with " * " so it survives Roaster's + // preformatted-block handling (Roaster does not auto-add asterisk prefix inside

).
+      String prefixedCode =
+          Arrays.stream(resolvedCode.split("\n", -1))
+              .map(line -> " * " + line)
+              .collect(Collectors.joining("\n"));
+      // Add the code example to the Javadoc with a blank line separator before 

. + String currentText = source.getJavaDoc().getText(); + String exampleText = "

Example:

{@code\n" + prefixedCode + "\n * }
"; + if (StringUtils.isNotBlank(currentText)) { + source.getJavaDoc().setText(currentText + "\n\n" + exampleText); + } else { + source.getJavaDoc().setText(exampleText); + } + } } private void applyAnnotations( diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java index 8769949a..69cd0c14 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java @@ -30,7 +30,7 @@ import org.javahelpers.simple.builders.processor.classgen.roaster.exceptions.RoasterMapperException; import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; import org.javahelpers.simple.builders.processor.model.annotation.InterfaceName; -import org.javahelpers.simple.builders.processor.model.method.MethodCodeDto; +import org.javahelpers.simple.builders.processor.model.method.CodeTemplateDto; 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; @@ -164,12 +164,12 @@ public static String mapInterfaceToTypeName(InterfaceName interfaceName) { } /** - * Resolves the JavaPoet-style named template used in MethodCodeDto to plain Java source code. + * Resolves the JavaPoet-style named template used in CodeTemplateDto to plain Java source code. * * @param codeDto code template DTO * @return resolved Java source code */ - public static String resolveCodeTemplate(MethodCodeDto codeDto) { + public static String resolveCodeTemplate(CodeTemplateDto codeDto) { String code = codeDto.getCodeFormat(); for (MethodCodePlaceholder placeHolderValue : codeDto.getCodeArguments()) { String label = placeHolderValue.getLabel(); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGenerator.java index c6e90652..69c8db66 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGenerator.java @@ -26,7 +26,7 @@ import java.util.List; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -84,5 +84,6 @@ public non-sealed interface MethodGenerator extends Generator { * @param context the processing context containing configuration and utilities * @return list of generated methods (may be empty but should not be null) */ - List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context); + List generateMethods( + FieldDto field, TypeName builderType, ProcessingContext context); } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ClassJavaDocEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ClassJavaDocEnhancer.java index 71ae20f9..8e22db59 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ClassJavaDocEnhancer.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ClassJavaDocEnhancer.java @@ -26,7 +26,10 @@ import org.javahelpers.simple.builders.processor.generators.BuilderEnhancer; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; +import org.javahelpers.simple.builders.processor.model.core.FieldDto; +import org.javahelpers.simple.builders.processor.model.javadoc.JavadocCodeBlockDto; 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.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -71,7 +74,7 @@ */ public class ClassJavaDocEnhancer implements BuilderEnhancer { - private static final int PRIORITY = 200; + private static final int PRIORITY = 10; @Override public int getPriority() { @@ -88,6 +91,33 @@ public boolean appliesTo( public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) { TypeName targetType = builderDto.getBuildingTargetTypeName(); JavadocDto javadoc = createClassJavadoc(targetType); + String indentionString = " "; + + // Synthesise example blocks from the fluent-chain fragments stored on methods. This keeps the + // per-method "builder.field(value);" example and the class-level kitchen-sink chain in sync + // with a single source of truth (the fragment on the BuilderMethodDto). + // Note: Methods are stored in FieldDto objects at this point (before finalizeDefinition). + JavadocCodeBlockDto classExampleBlock = new JavadocCodeBlockDto(); + for (FieldDto field : builderDto.getAllFieldsForBuilder()) { + for (BuilderMethodDto method : field.getMethods()) { + String fragment = method.getExampleChainFragment(); + if (fragment == null) { + continue; + } + // Class-level aggregation: indent and collect all fragments + classExampleBlock.append("%s.%s".formatted(indentionString, fragment)); + } + } + + if (classExampleBlock.hasCode()) { + String builderTypeName = builderDto.getBuilderTypeName().getClassName(); + String openingLine = + "%s result = %s.create()".formatted(targetType.getClassName(), builderTypeName); + classExampleBlock.setCodeFormat(openingLine + "\n" + classExampleBlock.getCodeFormat()); + classExampleBlock.append("%s.build();", indentionString); + javadoc.setExampleUsageCodeBlock(classExampleBlock); + } + builderDto.setClassJavadoc(javadoc); } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ConditionalEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ConditionalEnhancer.java index 94a38881..9cacada7 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ConditionalEnhancer.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ConditionalEnhancer.java @@ -30,7 +30,7 @@ import org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -103,11 +103,11 @@ public boolean appliesTo( @Override public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) { // Add conditional(BooleanSupplier, Consumer, Consumer) method - MethodDto conditionalMethod = createConditionalMethod(builderDto); + BuilderMethodDto conditionalMethod = createConditionalMethod(builderDto); builderDto.addMethod(conditionalMethod); // Add conditional(BooleanSupplier, Consumer) method - MethodDto conditionalPositiveMethod = createConditionalPositiveOnlyMethod(builderDto); + BuilderMethodDto conditionalPositiveMethod = createConditionalPositiveOnlyMethod(builderDto); builderDto.addMethod(conditionalPositiveMethod); context.debug( @@ -115,10 +115,10 @@ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext co } /** Creates the conditional(BooleanSupplier, Consumer, Consumer) method. */ - private MethodDto createConditionalMethod(BuilderDefinitionDto builderDto) { - MethodDto method = new MethodDto("conditional", builderDto.getBuilderTypeName()); + private BuilderMethodDto createConditionalMethod(BuilderDefinitionDto builderDto) { + BuilderMethodDto method = new BuilderMethodDto("conditional", builderDto.getBuilderTypeName()); method.setOrdering(ORDERING_CONDITIONAL); - method.setPriority(MethodDto.PRIORITY_HIGHEST); + method.setPriority(BuilderMethodDto.PRIORITY_HIGHEST); method.setModifier(AccessModifier.PUBLIC); // Add parameters @@ -147,10 +147,10 @@ private MethodDto createConditionalMethod(BuilderDefinitionDto builderDto) { } /** Creates the conditional(BooleanSupplier, Consumer) method. */ - private MethodDto createConditionalPositiveOnlyMethod(BuilderDefinitionDto builderDto) { - MethodDto method = new MethodDto("conditional", builderDto.getBuilderTypeName()); + private BuilderMethodDto createConditionalPositiveOnlyMethod(BuilderDefinitionDto builderDto) { + BuilderMethodDto method = new BuilderMethodDto("conditional", builderDto.getBuilderTypeName()); method.setOrdering(ORDERING_CONDITIONAL_POSITIVE_ONLY); - method.setPriority(MethodDto.PRIORITY_HIGHEST); + method.setPriority(BuilderMethodDto.PRIORITY_HIGHEST); method.setModifier(AccessModifier.PUBLIC); // Add parameters @@ -169,7 +169,8 @@ private MethodDto createConditionalPositiveOnlyMethod(BuilderDefinitionDto build } /** Adds parameters for the conditional(BooleanSupplier, Consumer, Consumer) method. */ - private void addConditionalPositiveNegativeParameters(MethodDto method, TypeName builderType) { + private void addConditionalPositiveNegativeParameters( + BuilderMethodDto method, TypeName builderType) { // BooleanSupplier condition parameter addParameter(method, "condition", JavaLangMapper.map2TypeName(BooleanSupplier.class)); // Consumer trueCase parameter @@ -179,7 +180,7 @@ private void addConditionalPositiveNegativeParameters(MethodDto method, TypeName } /** Adds parameters for the conditional(BooleanSupplier, Consumer) method. */ - private void addConditionalPositiveOnlyParameters(MethodDto method, TypeName builderType) { + private void addConditionalPositiveOnlyParameters(BuilderMethodDto method, TypeName builderType) { // BooleanSupplier condition parameter addParameter(method, "condition", JavaLangMapper.map2TypeName(BooleanSupplier.class)); // Consumer yesCondition parameter @@ -187,7 +188,7 @@ private void addConditionalPositiveOnlyParameters(MethodDto method, TypeName bui } /** Adds a parameter to the method. */ - private void addParameter(MethodDto method, String name, TypeName type) { + private void addParameter(BuilderMethodDto method, String name, TypeName type) { MethodParameterDto parameter = new MethodParameterDto(); parameter.setParameterName(name); parameter.setParameterTypeName(type); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/CoreMethodsEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/CoreMethodsEnhancer.java index 44ed603f..8ad0f497 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/CoreMethodsEnhancer.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/CoreMethodsEnhancer.java @@ -32,8 +32,9 @@ import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; import org.javahelpers.simple.builders.processor.model.core.FieldDto; +import org.javahelpers.simple.builders.processor.model.javadoc.JavadocCodeBlockDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.GenericParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -100,26 +101,26 @@ public boolean appliesTo( @Override public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) { // Add build() method - MethodDto buildMethod = createBuildMethod(builderDto); + BuilderMethodDto buildMethod = createBuildMethod(builderDto); builderDto.addMethod(buildMethod); // Add static create() method - MethodDto createMethod = createStaticCreateMethod(builderDto); + BuilderMethodDto createMethod = createStaticCreateMethod(builderDto); builderDto.addMethod(createMethod); // Add toString() method - MethodDto toStringMethod = createToStringMethod(builderDto); + BuilderMethodDto toStringMethod = createToStringMethod(builderDto); builderDto.addMethod(toStringMethod); } /** Creates the build() method. */ - protected MethodDto createBuildMethod(BuilderDefinitionDto builderDto) { + protected BuilderMethodDto createBuildMethod(BuilderDefinitionDto builderDto) { TypeName returnType = MethodGeneratorUtil.createGenericTypeName( builderDto.getBuildingTargetTypeName(), builderDto.getGenerics()); - MethodDto method = new MethodDto("build", returnType); + BuilderMethodDto method = new BuilderMethodDto("build", returnType); method.setOrdering(ORDERING_BUILD); - method.setPriority(MethodDto.PRIORITY_HIGHEST); + method.setPriority(BuilderMethodDto.PRIORITY_HIGHEST); method.setModifier(AccessModifier.PUBLIC); // Add @Override annotation only if implementing IBuilderBase interface @@ -197,19 +198,27 @@ protected MethodDto createBuildMethod(BuilderDefinitionDto builderDto) { method.addArgument("dtoBaseType", builderDto.getBuildingTargetTypeName()); method.addArgument("buildResultType", returnType); method.getMethodCodeDto().addCodeBlockImport(IllegalStateException.class); - method.setJavadoc(new JavadocDto("Builds the configured DTO instance.")); + JavadocDto javadoc = new JavadocDto("Builds the configured DTO instance."); + + // Add example to build() method + JavadocCodeBlockDto exampleBlock = new JavadocCodeBlockDto(); + String targetSimpleName = builderDto.getBuildingTargetTypeName().getClassName(); + exampleBlock.setCodeFormat("%s result = builder.build();".formatted(targetSimpleName)); + javadoc.setExampleUsageCodeBlock(exampleBlock); + + method.setJavadoc(javadoc); return method; } /** Creates the static create() method. */ - protected MethodDto createStaticCreateMethod(BuilderDefinitionDto builderDto) { + protected BuilderMethodDto createStaticCreateMethod(BuilderDefinitionDto builderDto) { TypeName returnType = MethodGeneratorUtil.createGenericTypeName( builderDto.getBuilderTypeName(), builderDto.getGenerics()); - MethodDto method = new MethodDto("create", returnType); + BuilderMethodDto method = new BuilderMethodDto("create", returnType); method.setOrdering(ORDERING_CREATE); - method.setPriority(MethodDto.PRIORITY_HIGHEST); + method.setPriority(BuilderMethodDto.PRIORITY_HIGHEST); method.setModifier(AccessModifier.PUBLIC); method.setStatic(true); @@ -228,19 +237,28 @@ protected MethodDto createStaticCreateMethod(BuilderDefinitionDto builderDto) { method.addArgument("builderType", builderDto.getBuilderTypeName()); String targetFullName = builderDto.getBuildingTargetTypeName().getFullQualifiedName(); + String builderSimpleName = builderDto.getBuilderTypeName().getClassName(); - method.setJavadoc( + JavadocDto javadoc = new JavadocDto("Creating a new builder for {@code %s}.", targetFullName) - .addReturn("builder for {@code %s}", targetFullName)); + .addReturn("builder for {@code %s}", targetFullName); + + // Add example to create() method + JavadocCodeBlockDto exampleBlock = new JavadocCodeBlockDto(); + exampleBlock.setCodeFormat( + "%s builder = %s.create();".formatted(builderSimpleName, builderSimpleName)); + javadoc.setExampleUsageCodeBlock(exampleBlock); + + method.setJavadoc(javadoc); return method; } /** Creates the toString() method. */ - protected MethodDto createToStringMethod(BuilderDefinitionDto builderDto) { - MethodDto method = new MethodDto("toString", TypeName.of(String.class)); + protected BuilderMethodDto createToStringMethod(BuilderDefinitionDto builderDto) { + BuilderMethodDto method = new BuilderMethodDto("toString", TypeName.of(String.class)); method.setOrdering(ORDERING_TO_STRING); - method.setPriority(MethodDto.PRIORITY_HIGHEST); + method.setPriority(BuilderMethodDto.PRIORITY_HIGHEST); method.setModifier(AccessModifier.PUBLIC); AnnotationDto overrideAnnotation = new AnnotationDto(); overrideAnnotation.setAnnotationType(JavaLangMapper.map2TypeName(Override.class)); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/WithInterfaceEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/WithInterfaceEnhancer.java index 5344d746..c0d07504 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/WithInterfaceEnhancer.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/WithInterfaceEnhancer.java @@ -29,8 +29,9 @@ import org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; +import org.javahelpers.simple.builders.processor.model.type.BuilderNestedTypeDto; import org.javahelpers.simple.builders.processor.model.type.NestedTypeDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; @@ -100,7 +101,7 @@ public boolean appliesTo( @Override public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) { - NestedTypeDto withInterface = createWithInterface(builderDto); + BuilderNestedTypeDto withInterface = createWithInterface(builderDto); builderDto.addNestedType(withInterface); } @@ -110,8 +111,8 @@ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext co * @param builderDto the builder definition * @return the nested type DTO for the With interface */ - private NestedTypeDto createWithInterface(BuilderDefinitionDto builderDto) { - NestedTypeDto withInterface = new NestedTypeDto(); + private BuilderNestedTypeDto createWithInterface(BuilderDefinitionDto builderDto) { + BuilderNestedTypeDto withInterface = new BuilderNestedTypeDto(); withInterface.setTypeName("With"); withInterface.setKind(NestedTypeDto.NestedTypeKind.INTERFACE); withInterface.setVisibility(AccessModifier.PUBLIC); @@ -120,11 +121,11 @@ private NestedTypeDto createWithInterface(BuilderDefinitionDto builderDto) { "Interface that can be implemented by the DTO to provide fluent modification methods.")); // Create the first method: DtoType with(Consumer b) - MethodDto withConsumerMethod = createWithConsumerMethod(builderDto); + BuilderMethodDto withConsumerMethod = createWithConsumerMethod(builderDto); withInterface.addMethod(withConsumerMethod); // Create the second method: BuilderType with() - MethodDto withBuilderMethod = createWithBuilderMethod(builderDto); + BuilderMethodDto withBuilderMethod = createWithBuilderMethod(builderDto); withInterface.addMethod(withBuilderMethod); return withInterface; @@ -136,10 +137,10 @@ private NestedTypeDto createWithInterface(BuilderDefinitionDto builderDto) { * @param builderDef the builder definition * @return the method definition */ - private MethodDto createWithConsumerMethod(BuilderDefinitionDto builderDef) { + private BuilderMethodDto createWithConsumerMethod(BuilderDefinitionDto builderDef) { // Return type is the DTO type TypeName dtoType = builderDef.getBuildingTargetTypeName(); - MethodDto method = new MethodDto("with", dtoType); + BuilderMethodDto method = new BuilderMethodDto("with", dtoType); // Parameter: Consumer b MethodParameterDto parameter = new MethodParameterDto(); @@ -183,9 +184,9 @@ private MethodDto createWithConsumerMethod(BuilderDefinitionDto builderDef) { * @param builderDef the builder definition * @return the method definition */ - private MethodDto createWithBuilderMethod(BuilderDefinitionDto builderDef) { + private BuilderMethodDto createWithBuilderMethod(BuilderDefinitionDto builderDef) { // Return type is the Builder type - MethodDto method = new MethodDto("with", builderDef.getBuilderTypeName()); + BuilderMethodDto method = new BuilderMethodDto("with", builderDef.getBuilderTypeName()); // Add implementation with validation to catch wrong implementations method.setCode( diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/AddToCollectionGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/AddToCollectionGenerator.java index e835219b..8407de81 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/AddToCollectionGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/AddToCollectionGenerator.java @@ -24,7 +24,9 @@ package org.javahelpers.simple.builders.processor.generators.field; -import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.*; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.TRACKED_VALUE_TYPE; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.addExampleChainFragment; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.getMethodAccessModifier; import java.util.ArrayList; import java.util.List; @@ -33,7 +35,7 @@ import org.javahelpers.simple.builders.processor.generators.util.JavadocConstants; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameList; @@ -100,14 +102,14 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { - List methods = new ArrayList<>(); + List methods = new ArrayList<>(); TypeName fieldType = field.getFieldType(); if (fieldType instanceof TypeNameList listType && listType.isParameterized()) { - MethodDto addMethod = + BuilderMethodDto addMethod = createAddToCollectionMethod( field.getOriginalFieldName(), field.getFieldNameInBuilder(), @@ -117,7 +119,7 @@ public List generateMethods( context); methods.add(addMethod); } else if (fieldType instanceof TypeNameSet setType && setType.isParameterized()) { - MethodDto addMethod = + BuilderMethodDto addMethod = createAddToCollectionMethod( field.getOriginalFieldName(), field.getFieldNameInBuilder(), @@ -131,7 +133,7 @@ public List generateMethods( return methods; } - private MethodDto createAddToCollectionMethod( + private BuilderMethodDto createAddToCollectionMethod( String originalFieldName, String fieldNameInBuilder, TypeName fieldType, @@ -139,7 +141,7 @@ private MethodDto createAddToCollectionMethod( TypeName builderType, ProcessingContext context) { String methodName = "add2" + StringUtils.capitalize(originalFieldName); - MethodDto methodDto = new MethodDto(methodName, builderType); + BuilderMethodDto methodDto = new BuilderMethodDto(methodName, builderType); MethodParameterDto parameter = new MethodParameterDto(); parameter.setParameterName("element"); @@ -180,13 +182,17 @@ private MethodDto createAddToCollectionMethod( methodDto.addArgument("elementType", elementType); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); methodDto.getMethodCodeDto().addCodeBlockImport(collectionImplType); - methodDto.setPriority(MethodDto.PRIORITY_MEDIUM); + methodDto.setPriority(BuilderMethodDto.PRIORITY_MEDIUM); methodDto.setJavadoc( new JavadocDto("Adds a single element to %s.", originalFieldName) .addParam("element", "the element to add") .addReturn(JavadocConstants.RETURN_BUILDER_INSTANCE)); + // Store the fluent-chain fragment so the class-level enhancer can synthesise both the + // method-level example block and the class-level kitchen-sink chain from one source of truth. + addExampleChainFragment(methodDto, elementType); + return methodDto; } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java index d6d02486..6c951c5a 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java @@ -33,7 +33,7 @@ import org.javahelpers.simple.builders.processor.generators.util.JavadocConstants; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; @@ -95,7 +95,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { TypeName fieldType = field.getFieldType(); @@ -107,14 +107,14 @@ public List generateMethods( TypeName elementType = arrayType.getTypeOfArray(); TypeName collectionBuilderType = map2TypeName(ArrayListBuilder.class); - MethodDto method = + BuilderMethodDto method = createFieldConsumerWithArrayBuilder( field, collectionBuilderType, elementType, builderType, context); return List.of(method); } - private MethodDto createFieldConsumerWithArrayBuilder( + private BuilderMethodDto createFieldConsumerWithArrayBuilder( FieldDto field, TypeName collectionBuilderType, TypeName elementType, @@ -129,10 +129,8 @@ private MethodDto createFieldConsumerWithArrayBuilder( parameter.setParameterName(fieldName + BUILDER_SUFFIX + SUFFIX_CONSUMER); parameter.setParameterTypeName(consumerType); - MethodDto methodDto = - new MethodDto(generateBuilderMethodName(fieldName, context), returnBuilderType); + BuilderMethodDto methodDto = createBuilderMethod(fieldName, returnBuilderType, context); methodDto.addParameter(parameter); - methodDto.setModifier(getMethodAccessModifier(context)); methodDto.setCode( """ $helperType:T builder = this.$fieldName:N.isSet() @@ -151,7 +149,7 @@ private MethodDto createFieldConsumerWithArrayBuilder( methodDto.addArgument("helperType", builderTypeGeneric); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); methodDto.addArgument("elementType", elementType); - methodDto.setPriority(MethodDto.PRIORITY_MEDIUM); + methodDto.setPriority(BuilderMethodDto.PRIORITY_MEDIUM); methodDto.setJavadoc( new JavadocDto( "Sets the value for %s using the fluent builder consumer.", fieldName) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java index 379172ad..772925af 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java @@ -32,7 +32,7 @@ import org.javahelpers.simple.builders.processor.generators.util.JavadocConstants; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; @@ -87,7 +87,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { TypeName fieldType = field.getFieldType(); @@ -99,13 +99,13 @@ public List generateMethods( TypeName elementType = arrayType.getTypeOfArray(); TypeNameGeneric listType = new TypeNameGeneric(map2TypeName(List.class), elementType); - MethodDto method = + BuilderMethodDto method = createFieldSetterForArrayFromList(field, listType, elementType, builderType, context); return List.of(method); } - private MethodDto createFieldSetterForArrayFromList( + private BuilderMethodDto createFieldSetterForArrayFromList( FieldDto field, TypeName listType, TypeName elementType, @@ -117,9 +117,8 @@ private MethodDto createFieldSetterForArrayFromList( parameter.setParameterName(fieldName); parameter.setParameterTypeName(listType); - MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType); + BuilderMethodDto methodDto = createBuilderMethod(fieldName, builderType, context); methodDto.addParameter(parameter); - methodDto.setModifier(getMethodAccessModifier(context)); methodDto.setCode( """ this.$fieldName:N = $builderFieldWrapper:T.changedValue($dtoMethodParams:N.toArray(new $elementType:T[0])); @@ -129,12 +128,16 @@ private MethodDto createFieldSetterForArrayFromList( methodDto.addArgument("dtoMethodParams", fieldName); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); methodDto.addArgument("elementType", elementType); - methodDto.setPriority(MethodDto.PRIORITY_HIGH); + methodDto.setPriority(BuilderMethodDto.PRIORITY_HIGH); String fieldJavadocDesc = field.getJavaDocDescriptionOrFieldName(); methodDto.setJavadoc( new JavadocDto("Sets the value for %s.", fieldName) .addParam(parameter.getParameterName(), fieldJavadocDesc) .addReturn(JavadocConstants.RETURN_BUILDER_INSTANCE)); + + // Add example fragment for array conversion method + addExampleChainFragment(methodDto, elementType); + return methodDto; } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/BasicSetterGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/BasicSetterGenerator.java index cf1bd0b9..9f66352f 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/BasicSetterGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/BasicSetterGenerator.java @@ -24,13 +24,12 @@ package org.javahelpers.simple.builders.processor.generators.field; -import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.*; - import java.util.Collections; import java.util.List; import org.javahelpers.simple.builders.processor.generators.MethodGenerator; +import org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -84,13 +83,17 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { - MethodDto setterMethod = - createBuilderMethodForFieldWithTransform( + BuilderMethodDto setterMethod = + MethodGeneratorUtil.createBuilderMethodForFieldWithTransform( field, null, field.getFieldType(), builderType, context); + // Store the fluent-chain fragment so the class-level enhancer can synthesise both the + // method-level example block and the class-level kitchen-sink chain from one source of truth. + MethodGeneratorUtil.addExampleChainFragment(setterMethod, field.getFieldType()); + return Collections.singletonList(setterMethod); } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/FieldInstanceConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/FieldInstanceConsumerGenerator.java index 756f3e90..09f5bbd2 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/FieldInstanceConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/FieldInstanceConsumerGenerator.java @@ -30,7 +30,7 @@ import java.util.List; import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameList; import org.javahelpers.simple.builders.processor.model.type.TypeNameMap; @@ -127,13 +127,14 @@ private boolean hasSpecificCollectionConsumer(FieldDto field, ProcessingContext } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { if (!field.getFieldType().hasEmptyConstructor()) { return Collections.emptyList(); } - MethodDto method = createSimpleFieldConsumer(field, field.getFieldType(), builderType, context); + BuilderMethodDto method = + createSimpleFieldConsumer(field, field.getFieldType(), builderType, context); return Collections.singletonList(method); } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ListConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ListConsumerGenerator.java index 820d9504..45c1d24c 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ListConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ListConsumerGenerator.java @@ -25,17 +25,20 @@ package org.javahelpers.simple.builders.processor.generators.field; import static org.javahelpers.simple.builders.processor.analysis.JavaLangMapper.map2TypeName; -import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.*; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.addExampleChainFragmentTemplate; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.createFieldConsumerWithBuilder; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.createFieldConsumerWithElementBuilders; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.core.builders.ArrayListBuilder; import org.javahelpers.simple.builders.core.builders.ArrayListBuilderWithElementBuilders; import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; import org.javahelpers.simple.builders.processor.model.type.TypeNameList; @@ -113,7 +116,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { if (!(field.getFieldType() instanceof TypeNameList fieldTypeGeneric && fieldTypeGeneric.isParameterized())) { @@ -130,14 +133,18 @@ public List generateMethods( map2TypeName(ArrayListBuilderWithElementBuilders.class), elementType, elementBuilderType.get()); - MethodDto method = + BuilderMethodDto method = createFieldConsumerWithElementBuilders( field, collectionBuilderType, elementBuilderType.get(), builderType, context); + + // Add method-level example for list consumer + addExampleToListConsumerWithBuilder(method, elementBuilderType.get()); + return List.of(method); } else if (context.getConfiguration().shouldUseArrayListBuilder()) { TypeName arrayListBuilderType = map2TypeName(ArrayListBuilder.class); TypeNameGeneric builderTypeGeneric = new TypeNameGeneric(arrayListBuilderType, elementType); - MethodDto method = + BuilderMethodDto method = createFieldConsumerWithBuilder( field, builderTypeGeneric, @@ -146,9 +153,38 @@ public List generateMethods( Map.of(), builderType, context); + + // Add method-level example for list consumer + addExampleToListConsumerWithSimpleValue(method, elementType); + return List.of(method); } return Collections.emptyList(); } + + /** + * Adds example for list consumer methods where the element type has its own builder. + * + * @param method the method to add the example to + * @param elementBuilderType the element builder type + */ + private void addExampleToListConsumerWithBuilder( + BuilderMethodDto method, TypeName elementBuilderType) { + String builderVar = StringUtils.uncapitalize(elementBuilderType.getClassName()); + addExampleChainFragmentTemplate( + method, "#{methodName}(t -> t.add(" + builderVar + " -> " + builderVar + "))"); + } + + /** + * Adds example for list consumer methods where the element type is a simple value. + * + * @param method the method to add the example to + * @param elementType the element type + */ + private void addExampleToListConsumerWithSimpleValue( + BuilderMethodDto method, TypeName elementType) { + addExampleChainFragmentTemplate( + method, "#{methodName}(t -> t.add(#{exampleValue}))", elementType); + } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/MapConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/MapConsumerGenerator.java index 3f3181e4..ac80efb4 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/MapConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/MapConsumerGenerator.java @@ -34,7 +34,7 @@ import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.BuilderConfiguration; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; import org.javahelpers.simple.builders.processor.model.type.TypeNameMap; @@ -131,7 +131,7 @@ public boolean appliesTo( * @return list of generated methods */ @Override - public List generateMethods( + public List generateMethods( final FieldDto field, final TypeName builderType, final ProcessingContext context) { if (!(field.getFieldType() instanceof TypeNameMap fieldTypeGeneric && fieldTypeGeneric.isParameterized())) { @@ -143,7 +143,7 @@ public List generateMethods( map2TypeName(HashMapBuilder.class), fieldTypeGeneric.getKeyType(), fieldTypeGeneric.getValueType()); - MethodDto mapConsumerWithBuilder = + BuilderMethodDto mapConsumerWithBuilder = createFieldConsumerWithBuilder( field, builderTargetTypeName, diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/NestedBuilderConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/NestedBuilderConsumerGenerator.java index ba32d321..7c645ccb 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/NestedBuilderConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/NestedBuilderConsumerGenerator.java @@ -30,9 +30,10 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -95,7 +96,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { Optional fieldBuilderOpt = field.getFieldType().getBuilderType(); if (fieldBuilderOpt.isEmpty()) { @@ -103,7 +104,7 @@ public List generateMethods( } TypeName fieldBuilderType = fieldBuilderOpt.get(); - MethodDto method = + BuilderMethodDto method = createFieldConsumerWithBuilder( field, fieldBuilderType, @@ -112,6 +113,12 @@ public List generateMethods( Map.of(), builderType, context); + + // Add example fragment showing the consumer lambda pattern + String builderVar = StringUtils.uncapitalize(fieldBuilderType.getClassName()); + addExampleChainFragmentTemplate( + method, "#{methodName}(" + builderVar + " -> " + builderVar + ")"); + return List.of(method); } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/OptionalHelperGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/OptionalHelperGenerator.java index d60bb31e..8e4771df 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/OptionalHelperGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/OptionalHelperGenerator.java @@ -33,7 +33,7 @@ import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -91,7 +91,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { TypeNameGeneric genericType = (TypeNameGeneric) field.getFieldType(); @@ -102,13 +102,16 @@ public List generateMethods( } TypeName innerType = innerTypes.get(0); - MethodDto method = + BuilderMethodDto method = MethodGeneratorUtil.createBuilderMethodForFieldWithTransform( field, "Optional.ofNullable(%s)", innerType, builderType, context); // Add code block import for Optional.ofNullable method.getMethodCodeDto().addCodeBlockImport(Optional.class); + // Add example fragment for Optional helper method + MethodGeneratorUtil.addExampleChainFragment(method, innerType); + return Collections.singletonList(method); } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SetConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SetConsumerGenerator.java index a02cf029..6ccf30b5 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SetConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SetConsumerGenerator.java @@ -31,11 +31,12 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.core.builders.HashSetBuilder; import org.javahelpers.simple.builders.core.builders.HashSetBuilderWithElementBuilders; import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; import org.javahelpers.simple.builders.processor.model.type.TypeNameSet; @@ -111,7 +112,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { if (!(field.getFieldType() instanceof TypeNameSet fieldTypeGeneric && fieldTypeGeneric.isParameterized())) { @@ -128,14 +129,15 @@ public List generateMethods( map2TypeName(HashSetBuilderWithElementBuilders.class), elementType, elementBuilderType.get()); - MethodDto method = + BuilderMethodDto method = createFieldConsumerWithElementBuilders( field, collectionBuilderType, elementBuilderType.get(), builderType, context); + addExampleToSetConsumerWithBuilder(method, elementBuilderType.get()); return List.of(method); } else if (context.getConfiguration().shouldUseHashSetBuilder()) { TypeName hashSetBuilderType = map2TypeName(HashSetBuilder.class); TypeNameGeneric builderTypeGeneric = new TypeNameGeneric(hashSetBuilderType, elementType); - MethodDto method = + BuilderMethodDto method = createFieldConsumerWithBuilder( field, builderTypeGeneric, @@ -144,9 +146,35 @@ public List generateMethods( Map.of(), builderType, context); + addExampleToSetConsumerWithSimpleValue(method, elementType); return List.of(method); } return Collections.emptyList(); } + + /** + * Adds example for set consumer methods where the element type has its own builder. + * + * @param method the method to add the example to + * @param elementBuilderType the element builder type + */ + private void addExampleToSetConsumerWithBuilder( + BuilderMethodDto method, TypeName elementBuilderType) { + String builderVar = StringUtils.uncapitalize(elementBuilderType.getClassName()); + addExampleChainFragmentTemplate( + method, "#{methodName}(t -> t.add(" + builderVar + " -> " + builderVar + "))"); + } + + /** + * Adds example for set consumer methods where the element type is a simple value. + * + * @param method the method to add the example to + * @param elementType the element type + */ + private void addExampleToSetConsumerWithSimpleValue( + BuilderMethodDto method, TypeName elementType) { + addExampleChainFragmentTemplate( + method, "#{methodName}(t -> t.add(#{exampleValue}))", elementType); + } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringBuilderConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringBuilderConsumerGenerator.java index 49eea86f..1fafcccf 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringBuilderConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringBuilderConsumerGenerator.java @@ -34,7 +34,7 @@ import org.javahelpers.simple.builders.processor.generators.util.JavadocConstants; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; @@ -105,13 +105,13 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { String transform = isOptionalString(field.getFieldType()) ? "Optional.of(builder.toString())" : "builder.toString()"; - MethodDto method = + BuilderMethodDto method = createStringBuilderConsumer( field.getOriginalFieldName(), field.getFieldNameInBuilder(), @@ -128,7 +128,7 @@ public List generateMethods( return List.of(method); } - private MethodDto createStringBuilderConsumer( + private BuilderMethodDto createStringBuilderConsumer( String fieldName, String fieldNameInBuilder, String fieldJavadoc, @@ -140,9 +140,8 @@ private MethodDto createStringBuilderConsumer( MethodParameterDto parameter = new MethodParameterDto(); parameter.setParameterName(fieldName + "StringBuilderConsumer"); parameter.setParameterTypeName(consumerType); - MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType); + BuilderMethodDto methodDto = createBuilderMethod(fieldName, builderType, context); methodDto.addParameter(parameter); - methodDto.setModifier(getMethodAccessModifier(context)); methodDto.setCode( """ StringBuilder builder = new StringBuilder(); @@ -155,13 +154,17 @@ private MethodDto createStringBuilderConsumer( methodDto.addArgument("transform", transform); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); methodDto.setReturnType(builderType); - methodDto.setPriority(MethodDto.PRIORITY_LOW); + methodDto.setPriority(BuilderMethodDto.PRIORITY_LOW); methodDto.setJavadoc( new JavadocDto( "Sets the value for %s by executing the provided consumer.", fieldName) .addParam( parameter.getParameterName(), "consumer providing an instance of %s", fieldJavadoc) .addReturn(JavadocConstants.RETURN_BUILDER_INSTANCE)); + + // Add example fragment for StringBuilder consumer method + addExampleChainFragmentTemplate(methodDto, "#{methodName}(sb -> sb.append(\"text\"))"); + return methodDto; } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringFormatHelperGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringFormatHelperGenerator.java index 5ee2fd9f..32eeeaef 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringFormatHelperGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringFormatHelperGenerator.java @@ -38,7 +38,7 @@ import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; @@ -112,14 +112,14 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { - List methods = new ArrayList<>(); + List methods = new ArrayList<>(); TypeName fieldType = field.getFieldType(); if (isString(fieldType) && !(fieldType instanceof TypeNameArray)) { - MethodDto method = + BuilderMethodDto method = createStringFormatMethodWithTransform( field.getOriginalFieldName(), field.getFieldNameInBuilder(), @@ -132,7 +132,7 @@ public List generateMethods( TypeNameGeneric genericType = (TypeNameGeneric) fieldType; List innerTypes = genericType.getInnerTypeArguments(); if (!innerTypes.isEmpty() && isString(innerTypes.get(0))) { - MethodDto method = + BuilderMethodDto method = createStringFormatMethodWithTransform( field.getOriginalFieldName(), field.getFieldNameInBuilder(), @@ -162,7 +162,7 @@ public List generateMethods( * @param context processing context * @return the method DTO for the String.format helper */ - private MethodDto createStringFormatMethodWithTransform( + private BuilderMethodDto createStringFormatMethodWithTransform( String fieldName, String fieldNameInBuilder, String transform, @@ -182,10 +182,9 @@ private MethodDto createStringFormatMethodWithTransform( argsParam.setParameterName("args"); argsParam.setParameterTypeName(new TypeNameArray(TypeName.of(Object.class))); - MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType); + BuilderMethodDto methodDto = createBuilderMethod(fieldName, builderType, context); methodDto.addParameter(formatParam); methodDto.addParameter(argsParam); - methodDto.setModifier(getMethodAccessModifier(context)); methodDto.setCode( """ this.$fieldName:N = $builderFieldWrapper:T.changedValue($transform:N); @@ -194,7 +193,7 @@ private MethodDto createStringFormatMethodWithTransform( methodDto.addArgument("fieldName", fieldNameInBuilder); methodDto.addArgument("transform", transform); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); - methodDto.setPriority(MethodDto.PRIORITY_HIGH); + methodDto.setPriority(BuilderMethodDto.PRIORITY_HIGH); methodDto.setJavadoc( new JavadocDto( "Sets the String value for %s by using String.format(format, args).\nSee {@link String#format(String, Object...)} for details.", @@ -204,6 +203,10 @@ private MethodDto createStringFormatMethodWithTransform( argsParam.getParameterName(), "Arguments referenced by the format specifiers in the format string.") .addReturn(JavadocConstants.RETURN_BUILDER_INSTANCE)); + + // Add example fragment for String format method + addExampleChainFragmentTemplate(methodDto, "#{methodName}(\"Hello %s\", \"World\")"); + return methodDto; } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SupplierMethodGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SupplierMethodGenerator.java index 101468dd..66878747 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SupplierMethodGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SupplierMethodGenerator.java @@ -25,7 +25,10 @@ package org.javahelpers.simple.builders.processor.generators.field; import static org.javahelpers.simple.builders.processor.analysis.JavaLangMapper.map2TypeName; -import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.*; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.SUFFIX_SUPPLIER; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.TRACKED_VALUE_TYPE; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.addExampleChainFragmentWithSupplier; +import static org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil.createBuilderMethod; import java.util.Collections; import java.util.List; @@ -34,7 +37,7 @@ import org.javahelpers.simple.builders.processor.generators.util.JavadocConstants; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; @@ -90,10 +93,10 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { - MethodDto supplierMethod = + BuilderMethodDto supplierMethod = createFieldSupplier( field.getOriginalFieldName(), field.getFieldNameInBuilder(), @@ -117,7 +120,7 @@ public List generateMethods( * @param context processing context * @return the method DTO for the supplier */ - private MethodDto createFieldSupplier( + private BuilderMethodDto createFieldSupplier( String fieldName, String fieldNameInBuilder, String fieldJavaDoc, @@ -131,9 +134,8 @@ private MethodDto createFieldSupplier( parameter.setParameterName(parameterName); parameter.setParameterTypeName(supplierType); - MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType); + BuilderMethodDto methodDto = createBuilderMethod(fieldName, builderType, context); methodDto.addParameter(parameter); - methodDto.setModifier(getMethodAccessModifier(context)); methodDto.setCode( """ @@ -143,7 +145,7 @@ private MethodDto createFieldSupplier( methodDto.addArgument("fieldName", fieldNameInBuilder); methodDto.addArgument("dtoMethodParam", parameterName); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); - methodDto.setPriority(MethodDto.PRIORITY_HIGH); + methodDto.setPriority(BuilderMethodDto.PRIORITY_HIGH); methodDto.setJavadoc( new JavadocDto( @@ -151,6 +153,8 @@ private MethodDto createFieldSupplier( .addParam(parameterName, "supplier for %s", fieldJavaDoc) .addReturn(JavadocConstants.RETURN_BUILDER_INSTANCE)); + addExampleChainFragmentWithSupplier(methodDto, fieldType); + return methodDto; } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/VarArgsHelperGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/VarArgsHelperGenerator.java index 3ef380d2..a6572d6e 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/VarArgsHelperGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/VarArgsHelperGenerator.java @@ -31,7 +31,7 @@ import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.generators.util.MethodGeneratorUtil; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; @@ -98,7 +98,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods( + public List generateMethods( FieldDto field, TypeName builderType, ProcessingContext context) { TypeName fieldType = field.getFieldType(); @@ -119,7 +119,7 @@ public List generateMethods( return Collections.emptyList(); } - MethodDto varArgsMethod = + BuilderMethodDto varArgsMethod = createFieldSetterByVarArgs(field, parameterType, builderType, context); return Collections.singletonList(varArgsMethod); } @@ -135,7 +135,7 @@ public List generateMethods( * @param context processing context * @return the method DTO for the setter */ - private MethodDto createFieldSetterByVarArgs( + private BuilderMethodDto createFieldSetterByVarArgs( FieldDto field, TypeName parameterType, TypeName builderType, ProcessingContext context) { String baseExpression; TypeName fieldType = field.getFieldType(); @@ -153,17 +153,24 @@ private MethodDto createFieldSetterByVarArgs( } String transform = MethodGeneratorUtil.wrapConcreteCollectionType(fieldType, baseExpression); - MethodDto method = + BuilderMethodDto method = MethodGeneratorUtil.createBuilderMethodForFieldWithTransform( field, transform, parameterType, builderType, context); - // Add code block imports for collection factory methods - if (fieldType instanceof TypeNameList) { + // Add code block imports and example fragments for collection factory methods + if (fieldType instanceof TypeNameList listType) { method.getMethodCodeDto().addCodeBlockImport(List.class); - } else if (fieldType instanceof TypeNameSet) { + MethodGeneratorUtil.addExampleChainFragmentVarArgs(method, listType.getElementType()); + } else if (fieldType instanceof TypeNameSet setType) { method.getMethodCodeDto().addCodeBlockImport(Set.class); - } else if (fieldType instanceof TypeNameMap) { + MethodGeneratorUtil.addExampleChainFragmentVarArgs(method, setType.getElementType()); + } else if (fieldType instanceof TypeNameMap mapType) { method.getMethodCodeDto().addCodeBlockImport(Map.class); + // For maps, only add example if key type is String + if (mapType.getKeyType().getClassName().equals("String")) { + MethodGeneratorUtil.addExampleChainFragmentTemplate( + method, "#{methodName}(Map.entry(\"key\", #{exampleValue}))", mapType.getValueType()); + } } return method; diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/registry/GeneratorRegistry.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/registry/GeneratorRegistry.java index b31b0494..ef0ebce8 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/registry/GeneratorRegistry.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/registry/GeneratorRegistry.java @@ -35,7 +35,7 @@ import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; import org.javahelpers.simple.builders.processor.model.core.FieldDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -90,9 +90,9 @@ public GeneratorRegistry(ProcessingContext context, ProcessingEnvironment proces * @param builderType the type of the builder being generated, should not be null * @return list of all generated methods from all applicable generators */ - public List generateAllMethods( + public List generateAllMethods( FieldDto field, TypeName dtoType, TypeName builderType) { - List allMethods = new ArrayList<>(); + List allMethods = new ArrayList<>(); context.debugStartOperation("Processing method generators"); for (MethodGenerator generator : methodGenerators) { @@ -102,7 +102,8 @@ public List generateAllMethods( "Applying: %s (priority: %d)", generator.getClass().getSimpleName(), generator.getPriority()); - List generatedMethods = generator.generateMethods(field, builderType, context); + List generatedMethods = + generator.generateMethods(field, builderType, context); if (CollectionUtils.isNotEmpty(generatedMethods)) { allMethods.addAll(generatedMethods); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/util/JavadocExampleValues.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/util/JavadocExampleValues.java new file mode 100644 index 00000000..a31044e8 --- /dev/null +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/util/JavadocExampleValues.java @@ -0,0 +1,194 @@ +/* + * MIT License + * + * Copyright (c) 2026 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.generators.util; + +import java.util.Map; +import java.util.Optional; +import org.javahelpers.simple.builders.processor.model.type.TypeName; +import org.javahelpers.simple.builders.processor.model.type.TypeNameList; +import org.javahelpers.simple.builders.processor.model.type.TypeNameMap; +import org.javahelpers.simple.builders.processor.model.type.TypeNamePrimitive; +import org.javahelpers.simple.builders.processor.model.type.TypeNamePrimitive.PrimitiveTypeEnum; +import org.javahelpers.simple.builders.processor.model.type.TypeNameSet; + +/** + * Provides default example values for Javadoc code examples. + * + *

This class provides sensible default values for common types to be used in generated Javadoc + * code examples. These values are designed to be realistic yet generic enough to work in most + * contexts. + */ +public final class JavadocExampleValues { + + private static final String STRING_EXAMPLE = "\"example value\""; + private static final String INT_EXAMPLE = "42"; + private static final String LONG_EXAMPLE = "42L"; + private static final String DOUBLE_EXAMPLE = "3.14"; + private static final String FLOAT_EXAMPLE = "3.14f"; + private static final String BOOLEAN_EXAMPLE = "true"; + private static final String CHAR_EXAMPLE = "'x'"; + private static final String BIGINTEGER_EXAMPLE = "BigInteger.valueOf(42)"; + private static final String BIGDECIMAL_EXAMPLE = "BigDecimal.valueOf(3.14)"; + private static final String UUID_EXAMPLE = "UUID.randomUUID()"; + private static final String LOCALDATE_EXAMPLE = "LocalDate.now()"; + private static final String LOCALTIME_EXAMPLE = "LocalTime.now()"; + private static final String LOCALDATETIME_EXAMPLE = "LocalDateTime.now()"; + + /** Map of primitive type enums to their example values. */ + private static final Map PRIMITIVE_EXAMPLES = + Map.of( + PrimitiveTypeEnum.INT, INT_EXAMPLE, + PrimitiveTypeEnum.LONG, LONG_EXAMPLE, + PrimitiveTypeEnum.DOUBLE, DOUBLE_EXAMPLE, + PrimitiveTypeEnum.FLOAT, FLOAT_EXAMPLE, + PrimitiveTypeEnum.BOOLEAN, BOOLEAN_EXAMPLE, + PrimitiveTypeEnum.CHAR, CHAR_EXAMPLE); + + /** Map of wrapper and common JDK type FQNs to their example values. */ + private static final Map COMMON_TYPE_EXAMPLES = + Map.ofEntries( + // Wrapper types (mirror primitives) + Map.entry("java.lang.Integer", INT_EXAMPLE), + Map.entry("java.lang.Long", LONG_EXAMPLE), + Map.entry("java.lang.Double", DOUBLE_EXAMPLE), + Map.entry("java.lang.Float", FLOAT_EXAMPLE), + Map.entry("java.lang.Boolean", BOOLEAN_EXAMPLE), + Map.entry("java.lang.Character", CHAR_EXAMPLE), + // Common JDK types + Map.entry("java.math.BigInteger", BIGINTEGER_EXAMPLE), + Map.entry("java.math.BigDecimal", BIGDECIMAL_EXAMPLE), + Map.entry("java.util.UUID", UUID_EXAMPLE), + Map.entry("java.time.LocalDate", LOCALDATE_EXAMPLE), + Map.entry("java.time.LocalTime", LOCALTIME_EXAMPLE), + Map.entry("java.time.LocalDateTime", LOCALDATETIME_EXAMPLE)); + + private JavadocExampleValues() { + // Utility class - prevent instantiation + } + + /** + * Returns an example value for the given type name, if available. + * + *

This method returns example values for: + * + *

    + *
  • Primitive types (int, long, double, float, boolean, char) + *
  • String type (returns {@value STRING_EXAMPLE}) + *
  • Wrapper types (Integer, Long, Double, Float, Boolean, Character) + *
  • Common JDK types (BigInteger, BigDecimal, UUID, LocalDate, LocalTime, LocalDateTime) + *
  • Collection types (List, Set) of any supported element type (e.g., {@code List.of("example + * value")}, {@code Set.of(42)}) + *
  • Map types where both key and value types have supported examples (e.g., {@code Map.of(42, + * "example value")}) + *
  • Optional types with a supported inner type (e.g., {@code Optional.of("example value")}) + *
  • Types with an empty constructor (e.g., {@code new AddressDto()}) + *
  • Types with a {@code @SimpleBuilder} annotation but no empty constructor (e.g., {@code + * AddressDtoBuilder.create().build()}) + *
+ * + * @param typeName the type name to get an example value for + * @return an Optional containing the example value, or empty if no example is available + */ + public static Optional getExampleValue(TypeName typeName) { + return resolvePrimitive(typeName) + .or(() -> resolveCollection(typeName)) + .or(() -> resolveOptional(typeName)) + .or(() -> resolveCommonType(typeName)) + .or(() -> resolveString(typeName)) + .or(() -> resolveEmptyConstructor(typeName)) + .or(() -> resolveBuilderType(typeName)); + } + + private static Optional resolvePrimitive(TypeName typeName) { + if (typeName instanceof TypeNamePrimitive primitive) { + return Optional.ofNullable(PRIMITIVE_EXAMPLES.get(primitive.getType())); + } + return Optional.empty(); + } + + private static Optional resolveCollection(TypeName typeName) { + if (typeName instanceof TypeNameList listType && listType.isParameterized()) { + return getExampleValue(listType.getElementType()) + .map(elementExample -> "List.of(" + elementExample + ")"); + } + if (typeName instanceof TypeNameSet setType && setType.isParameterized()) { + return getExampleValue(setType.getElementType()) + .map(elementExample -> "Set.of(" + elementExample + ")"); + } + if (typeName instanceof TypeNameMap mapType && mapType.isParameterized()) { + return resolveMap(mapType); + } + return Optional.empty(); + } + + private static Optional resolveMap(TypeNameMap mapType) { + Optional keyExample = getExampleValue(mapType.getKeyType()); + Optional valueExample = getExampleValue(mapType.getValueType()); + if (keyExample.isPresent() && valueExample.isPresent()) { + return Optional.of("Map.of(" + keyExample.get() + ", " + valueExample.get() + ")"); + } + return Optional.empty(); + } + + private static Optional resolveOptional(TypeName typeName) { + if ("java.util.Optional".equals(typeName.getFullQualifiedName()) + || "Optional".equals(typeName.getClassName())) { + return typeName + .getInnerType() + .flatMap(JavadocExampleValues::getExampleValue) + .map(inner -> "Optional.of(" + inner + ")"); + } + return Optional.empty(); + } + + private static Optional resolveCommonType(TypeName typeName) { + String fqn = typeName.getFullQualifiedName(); + if (fqn != null && COMMON_TYPE_EXAMPLES.containsKey(fqn)) { + return Optional.of(COMMON_TYPE_EXAMPLES.get(fqn)); + } + return Optional.empty(); + } + + private static Optional resolveString(TypeName typeName) { + if ("java.lang.String".equals(typeName.getFullQualifiedName()) + || "String".equals(typeName.getClassName())) { + return Optional.of(STRING_EXAMPLE); + } + return Optional.empty(); + } + + private static Optional resolveEmptyConstructor(TypeName typeName) { + if (typeName.hasEmptyConstructor()) { + return Optional.of("new " + typeName.getClassName() + "()"); + } + return Optional.empty(); + } + + private static Optional resolveBuilderType(TypeName typeName) { + return typeName + .getBuilderType() + .map(builderType -> builderType.getClassName() + ".create().build()"); + } +} diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/util/MethodGeneratorUtil.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/util/MethodGeneratorUtil.java index 759d3595..fc79e59a 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/util/MethodGeneratorUtil.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/util/MethodGeneratorUtil.java @@ -26,13 +26,14 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.core.enums.AccessModifier; import org.javahelpers.simple.builders.processor.analysis.JavaLangMapper; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.GenericParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; @@ -99,6 +100,23 @@ public static AccessModifier getMethodAccessModifier(ProcessingContext context) return context.getConfiguration().getMethodAccess(); } + /** + * Creates a {@link BuilderMethodDto} with the method name derived from the field name and + * configuration, and the access modifier set from the processing context. + * + * @param fieldName the field name to derive the method name from + * @param returnType the return type of the method + * @param context the processing context + * @return a new {@link BuilderMethodDto} with name and modifier set + */ + public static BuilderMethodDto createBuilderMethod( + String fieldName, TypeName returnType, ProcessingContext context) { + BuilderMethodDto method = + new BuilderMethodDto(generateBuilderMethodName(fieldName, context), returnType); + method.setModifier(getMethodAccessModifier(context)); + return method; + } + /** * Creates a generic TypeName from a base type and generic parameters. * @@ -136,7 +154,7 @@ public static TypeName createGenericTypeName( * @param context processing context * @return the method DTO for the setter */ - public static MethodDto createBuilderMethodForFieldWithTransform( + public static BuilderMethodDto createBuilderMethodForFieldWithTransform( FieldDto field, String transform, TypeName parameterType, @@ -151,11 +169,9 @@ public static MethodDto createBuilderMethodForFieldWithTransform( field.getParameterAnnotations().forEach(parameter::addAnnotation); } - MethodDto methodDto = - new MethodDto( - generateBuilderMethodName(field.getOriginalFieldName(), context), builderType); + BuilderMethodDto methodDto = + createBuilderMethod(field.getOriginalFieldName(), builderType, context); methodDto.addParameter(parameter); - methodDto.setModifier(getMethodAccessModifier(context)); String params; if (StringUtils.isBlank(transform)) { @@ -173,7 +189,8 @@ public static MethodDto createBuilderMethodForFieldWithTransform( methodDto.addArgument("dtoMethodParams", params); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); - methodDto.setPriority(transform == null ? MethodDto.PRIORITY_HIGHEST : MethodDto.PRIORITY_HIGH); + methodDto.setPriority( + transform == null ? BuilderMethodDto.PRIORITY_HIGHEST : BuilderMethodDto.PRIORITY_HIGH); String fieldJavadocDesc = field.getJavaDocDescriptionOrFieldName(); methodDto.setJavadoc( @@ -196,7 +213,7 @@ public static MethodDto createBuilderMethodForFieldWithTransform( * @param context the processing context * @return the method DTO for the consumer */ - public static MethodDto createFieldConsumerWithBuilder( + public static BuilderMethodDto createFieldConsumerWithBuilder( FieldDto field, TypeName fieldBuilderType, String existingValueConstructorArgs, @@ -208,11 +225,9 @@ public static MethodDto createFieldConsumerWithBuilder( MethodParameterDto parameter = new MethodParameterDto(); parameter.setParameterName(field.getFieldNameInBuilder() + BUILDER_SUFFIX + SUFFIX_CONSUMER); parameter.setParameterTypeName(consumerType); - MethodDto methodDto = - new MethodDto( - generateBuilderMethodName(field.getOriginalFieldName(), context), parentBuilderType); + BuilderMethodDto methodDto = + createBuilderMethod(field.getOriginalFieldName(), parentBuilderType, context); methodDto.addParameter(parameter); - methodDto.setModifier(getMethodAccessModifier(context)); String buildExpression = calculateBuildExpression(field.getFieldType()); @@ -232,7 +247,7 @@ public static MethodDto createFieldConsumerWithBuilder( methodDto.addArgument("buildExpression", buildExpression); additionalTemplateArguments.forEach(methodDto::addArgument); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); - methodDto.setPriority(MethodDto.PRIORITY_MEDIUM); + methodDto.setPriority(BuilderMethodDto.PRIORITY_MEDIUM); String fieldJavadocDesc = field.getJavaDocDescriptionOrFieldName(); methodDto.setJavadoc( new JavadocDto( @@ -310,7 +325,7 @@ public static String wrapConcreteCollectionType(TypeName fieldType, String baseE * @param context the processing context * @return the method DTO for the consumer */ - public static MethodDto createFieldConsumerWithElementBuilders( + public static BuilderMethodDto createFieldConsumerWithElementBuilders( FieldDto field, TypeName collectionBuilderType, TypeName elementBuilderType, @@ -338,18 +353,16 @@ public static MethodDto createFieldConsumerWithElementBuilders( * @param context the processing context * @return the method DTO for the simple field consumer */ - public static MethodDto createSimpleFieldConsumer( + public static BuilderMethodDto createSimpleFieldConsumer( FieldDto field, TypeName fieldType, TypeName builderType, ProcessingContext context) { TypeNameGeneric consumerType = createConsumerType(fieldType); MethodParameterDto parameter = new MethodParameterDto(); parameter.setParameterName(field.getOriginalFieldName() + SUFFIX_CONSUMER); parameter.setParameterTypeName(consumerType); - MethodDto methodDto = - new MethodDto( - generateBuilderMethodName(field.getOriginalFieldName(), context), builderType); + BuilderMethodDto methodDto = + createBuilderMethod(field.getOriginalFieldName(), builderType, context); methodDto.addParameter(parameter); - methodDto.setModifier(getMethodAccessModifier(context)); methodDto.setCode( """ @@ -364,7 +377,7 @@ public static MethodDto createSimpleFieldConsumer( methodDto.addArgument("dtoMethodParam", parameter.getParameterName()); methodDto.addArgument("helperType", fieldType); methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE); - methodDto.setPriority(MethodDto.PRIORITY_MEDIUM); + methodDto.setPriority(BuilderMethodDto.PRIORITY_MEDIUM); String fieldJavadocDesc = field.getJavaDocDescriptionOrFieldName(); methodDto.setJavadoc( @@ -379,4 +392,120 @@ public static MethodDto createSimpleFieldConsumer( return methodDto; } + + /** + * Adds the fluent-chain fragment for Javadoc examples to a method. + * + *

This helper method retrieves an example value for the given field type and formats it as a + * fluent-chain fragment (e.g., {@code methodName(exampleValue)}). The fragment is stored on the + * BuilderMethodDto for later use by the ClassJavaDocEnhancer to synthesize both method-level and + * class-level Javadoc examples. + * + *

If no example value is available for the field type, the fragment is not added. + * + * @param methodDto the method DTO to add the fragment to + * @param fieldType the field type to get an example value for + */ + public static void addExampleChainFragment(BuilderMethodDto methodDto, TypeName fieldType) { + addExampleChainFragmentTemplate(methodDto, "#{methodName}(#{exampleValue})", fieldType); + } + + /** + * Adds the fluent-chain fragment for Javadoc examples to a varargs method. + * + *

This helper method retrieves an example value for the given element type and formats it as a + * fluent-chain fragment showing two values (e.g., {@code methodName(value1, value2)}) to make it + * clear that the method accepts multiple arguments. The fragment is stored on the BuilderMethodDto for + * later use by the ClassJavaDocEnhancer to synthesize both method-level and class-level Javadoc + * examples. + * + *

If no example value is available for the element type, the fragment is not added. + * + * @param methodDto the method DTO to add the fragment to + * @param elementType the element type to get an example value for + */ + public static void addExampleChainFragmentVarArgs( + BuilderMethodDto methodDto, TypeName elementType) { + addExampleChainFragmentTemplate( + methodDto, "#{methodName}(#{exampleValue}, #{exampleValue})", elementType); + } + + /** + * Adds the fluent-chain fragment for Javadoc examples to a method using a supplier pattern. + * + *

This helper method retrieves an example value for the given field type and formats it as a + * fluent-chain fragment with a supplier. For types with an empty constructor, a method reference + * is used (e.g., {@code methodName(Type::new)}); otherwise a lambda is used (e.g., {@code + * methodName(() -> exampleValue)}). The fragment is stored on the BuilderMethodDto for later use by the + * ClassJavaDocEnhancer to synthesize both method-level and class-level Javadoc examples. + * + *

If no example value is available for the field type, the fragment is not added. + * + * @param methodDto the method DTO to add the fragment to + * @param fieldType the field type to get an example value for + */ + public static void addExampleChainFragmentWithSupplier( + BuilderMethodDto methodDto, TypeName fieldType) { + if (fieldType.hasEmptyConstructor()) { + addExampleChainFragmentTemplate( + methodDto, "#{methodName}(" + fieldType.getClassName() + "::new)"); + } else { + addExampleChainFragmentTemplate(methodDto, "#{methodName}(() -> #{exampleValue})", fieldType); + } + } + + /** + * Adds the fluent-chain fragment for Javadoc examples to a method using a template string. + * + *

This helper method replaces placeholders in the template string with actual values: + * + *

    + *
  • {@code #{methodName}} - replaced with the method name + *
  • {@code #{exampleValue}} - replaced with an example value for the given field type (if + * fieldType is provided) + *
+ * + *

The fragment is stored on the BuilderMethodDto for later use by the ClassJavaDocEnhancer to + * synthesize both method-level and class-level Javadoc examples. + * + * @param methodDto the method DTO to add the fragment to + * @param template the template string with placeholders (e.g., {@code + * #{methodName}(#{exampleValue})}) + * @param fieldType the field type to get an example value for (can be {@code null}) + */ + public static void addExampleChainFragmentTemplate( + BuilderMethodDto methodDto, String template, TypeName fieldType) { + String fragment = template.replace("#{methodName}", methodDto.getMethodName()); + + if (fieldType != null) { + Optional exampleValue = JavadocExampleValues.getExampleValue(fieldType); + if (exampleValue.isEmpty()) { + return; + } + fragment = fragment.replace("#{exampleValue}", exampleValue.get()); + } + + methodDto.setExampleChainFragment(fragment); + } + + /** + * Adds the fluent-chain fragment for Javadoc examples to a method using a template string. + * + *

This helper method replaces placeholders in the template string with actual values: + * + *

    + *
  • {@code #{methodName}} - replaced with the method name + *
+ * + *

The fragment is stored on the BuilderMethodDto for later use by the ClassJavaDocEnhancer to + * synthesize both method-level and class-level Javadoc examples. + * + * @param methodDto the method DTO to add the fragment to + * @param template the template string with placeholders (e.g., {@code #{methodName}(sb -> + * sb.append("text"))}) + */ + public static void addExampleChainFragmentTemplate(BuilderMethodDto methodDto, String template) { + String fragment = template.replace("#{methodName}", methodDto.getMethodName()); + methodDto.setExampleChainFragment(fragment); + } } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/AnnotationDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/AnnotationDto.java index 4df62235..9787249f 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/AnnotationDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/AnnotationDto.java @@ -31,9 +31,8 @@ import org.javahelpers.simple.builders.processor.model.type.TypeName; /** - * DTO representing an annotation to be copied from the target class field to the builder class - * field. Contains the annotation type and its members in a plain format suitable for code - * generation. + * DTO representing an annotation to be generated in the output class. Contains the annotation type + * and its members in a plain format suitable for code generation. */ public class AnnotationDto { /** The annotation type (fully qualified name). */ diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/InterfaceName.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/InterfaceName.java index c39d40e0..e164d51d 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/InterfaceName.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/annotation/InterfaceName.java @@ -36,15 +36,15 @@ * InterfaceName represents a Java interface type with package and class name information. * *

This type is specifically designed for interfaces and contains only interface-relevant - * information. Unlike {@link TypeName}, it doesn't include class-specific concepts like builders, - * constructors, or inner types. + * information. Unlike {@link TypeName}, it doesn't include class-specific concepts like + * constructors or inner types. * *

Typical usage includes: * *

    - *
  • IBuilderBase interface for builder contracts - *
  • Custom interfaces for builder extensions - *
  • Mixin interfaces for Jackson serialization + *
  • Base interfaces for generated class contracts + *
  • Custom interfaces for generated class extensions + *
  • Mixin interfaces for serialization frameworks *
*/ public class InterfaceName { diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderDefinitionDto.java index 7bc198e2..a8c524b4 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderDefinitionDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderDefinitionDto.java @@ -24,17 +24,39 @@ package org.javahelpers.simple.builders.processor.model.core; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; +import org.javahelpers.simple.builders.core.enums.AccessModifier; +import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; +import org.javahelpers.simple.builders.processor.model.annotation.InterfaceName; +import org.javahelpers.simple.builders.processor.model.imports.ImportStatement; +import org.javahelpers.simple.builders.processor.model.imports.RegularImport; +import org.javahelpers.simple.builders.processor.model.imports.StaticImport; +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.type.BuilderNestedTypeDto; +import org.javahelpers.simple.builders.processor.model.type.GenericParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; +import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; +import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; /** - * Builder-specific extension of GenerationTargetClassDto. + * Generation-side DTO holding all information for generating a builder. * - *

This class holds all information for generating a builder, including builder-specific fields - * and methods that are not part of the generic class generation. + *

This class does NOT extend {@link GenerationTargetClassDto}. Instead, it holds generation-side + * data (including {@link BuilderMethodDto} and {@link BuilderNestedTypeDto}) and produces a {@link + * GenerationTargetClassDto} (rendering DTO) during finalization in {@code + * BuilderDefinitionCreator.finalizeDefinition()}. */ -public class BuilderDefinitionDto extends GenerationTargetClassDto { +public class BuilderDefinitionDto { + + // --- Builder-specific fields --- + /** Target type of result of building by builder. Containing package and name of DTO. */ private TypeName buildingTargetTypeName; @@ -54,22 +76,170 @@ public class BuilderDefinitionDto extends GenerationTargetClassDto { /** Configuration for builder generation. */ private BuilderConfiguration configuration; + // --- Rendering-side fields (set during enhancement, mapped during finalization) --- + + /** Generated class FQN. */ + private TypeName typeName; + + /** Class visibility. */ + private AccessModifier classAccessModifier; + + /** Superclass, null if none. */ + private TypeName superType; + + /** Field declarations. */ + private final List classFields = new LinkedList<>(); + + /** Constructor definitions. */ + private final List constructors = new LinkedList<>(); + /** - * Getting type of builder. Delegates to base class typeName. + * Builder-level methods generated by enhancers (not tied to a specific field). These are + * BuilderMethodDto instances that will be mapped to MethodDto during finalization. + */ + private final List methods = new LinkedList<>(); + + /** Generic parameters declared on the target DTO (e.g., {@code }). */ + private final List generics = new LinkedList<>(); + + /** Imports (both regular and static imports). */ + private final Set imports = new LinkedHashSet<>(); + + /** + * Nested types generated by enhancers (generation-side). These are BuilderNestedTypeDto instances + * that will be mapped to NestedTypeDto during finalization. + */ + private final List nestedTypes = new LinkedList<>(); + + /** Class-level annotations. */ + private final Set classAnnotations = new LinkedHashSet<>(); + + /** Interfaces to be implemented by the generated builder class. */ + private final Set interfaces = new LinkedHashSet<>(); + + /** Class-level JavaDoc for the generated builder class. */ + private JavadocDto classJavadoc; + + /** + * Getting type of builder. * * @return package and name of builder. */ public TypeName getBuilderTypeName() { - return getTypeName(); + return typeName; } /** - * Setting type of builder. Delegates to base class typeName. + * Setting type of builder. * * @param builderClassName type of builder */ public void setBuilderTypeName(TypeName builderClassName) { - setTypeName(builderClassName); + this.typeName = builderClassName; + } + + public TypeName getTypeName() { + return getBuilderTypeName(); + } + + public void setTypeName(TypeName typeName) { + setBuilderTypeName(typeName); + } + + public AccessModifier getClassAccessModifier() { + return classAccessModifier; + } + + public void setClassAccessModifier(AccessModifier classAccessModifier) { + this.classAccessModifier = classAccessModifier; + } + + public TypeName getSuperType() { + return superType; + } + + public void setSuperType(TypeName superType) { + this.superType = superType; + } + + public List getClassFields() { + return classFields; + } + + public void addClassField(ClassFieldDto classField) { + this.classFields.add(classField); + } + + public List getConstructors() { + return constructors; + } + + public void addConstructor(ConstructorDto constructor) { + this.constructors.add(constructor); + } + + public Set getImports() { + return imports; + } + + public void addImport(ImportStatement importStatement) { + this.imports.add(importStatement); + } + + public void addImport(Class clazz) { + addImport(new RegularImport(TypeName.of(clazz))); + } + + public void addImport(TypeName typeName) { + if (typeName instanceof TypeNameGeneric generic) { + addImport(generic.getRawType()); + generic.getInnerTypeArguments().forEach(this::addImport); + } else if (typeName instanceof TypeNameArray array) { + addImport(array.getTypeOfArray()); + } else { + addImport(new RegularImport(typeName)); + } + } + + public void addStaticImport(Class clazz, String memberName) { + addImport(new StaticImport(TypeName.of(clazz), memberName)); + } + + public void addStaticImport(TypeName type, String memberName) { + TypeName typeRaw = new TypeName(type.getPackageName(), type.getClassName()); + addImport(new StaticImport(typeRaw, memberName)); + } + + public void addGeneric(GenericParameterDto generic) { + generics.add(generic); + } + + public List getGenerics() { + return generics; + } + + public Set getClassAnnotations() { + return classAnnotations; + } + + public void addClassAnnotation(AnnotationDto annotation) { + this.classAnnotations.add(annotation); + } + + public Set getInterfaces() { + return interfaces; + } + + public void addInterface(InterfaceName interfaceType) { + this.interfaces.add(interfaceType); + } + + public JavadocDto getClassJavadoc() { + return classJavadoc; + } + + public void setClassJavadoc(JavadocDto classJavadoc) { + this.classJavadoc = classJavadoc; } /** @@ -174,4 +344,44 @@ public BuilderConfiguration getConfiguration() { public void setConfiguration(BuilderConfiguration configuration) { this.configuration = configuration; } + + /** + * Adds a builder-level method generated by an enhancer. + * + * @param method the builder method to add + */ + public void addMethod(BuilderMethodDto method) { + this.methods.add(method); + } + + /** + * Returns all builder-level methods added by enhancers. + * + * @return list of builder-level methods + */ + public List getMethods() { + return Collections.unmodifiableList(methods); + } + + public void removeMethods(Collection toRemove) { + this.methods.removeAll(toRemove); + } + + /** + * Adds a nested type generated by an enhancer. + * + * @param nestedType the nested type to add + */ + public void addNestedType(BuilderNestedTypeDto nestedType) { + this.nestedTypes.add(nestedType); + } + + /** + * Returns all nested types added by enhancers. + * + * @return list of nested types + */ + public List getNestedTypes() { + return nestedTypes; + } } 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 new file mode 100644 index 00000000..4d67b6e8 --- /dev/null +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java @@ -0,0 +1,179 @@ +/* + * MIT License + * + * Copyright (c) 2026 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.model.core; + +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.MethodCodePlaceholder; +import org.javahelpers.simple.builders.processor.model.method.MethodCodeStringPlaceholder; +import org.javahelpers.simple.builders.processor.model.method.MethodCodeTypePlaceholder; +import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.type.BuilderNestedTypeDto; +import org.javahelpers.simple.builders.processor.model.type.NestedTypeDto; + +/** + * Maps generation-side DTOs ({@link BuilderDefinitionDto}, {@link BuilderMethodDto}, {@link + * BuilderNestedTypeDto}) to rendering-side DTOs ({@link GenerationTargetClassDto}, {@link + * MethodDto}, {@link NestedTypeDto}). + * + *

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. + */ +public class BuilderToGenerationTypeMapper { + + private BuilderToGenerationTypeMapper() { + // Utility class - prevent instantiation + } + + /** + * Maps a {@link BuilderDefinitionDto} (generation DTO) to a {@link GenerationTargetClassDto} + * (rendering DTO) for code generation. + * + *

This maps all rendering-relevant fields, converting {@link BuilderMethodDto} to {@link + * MethodDto} and {@link BuilderNestedTypeDto} to {@link NestedTypeDto}. + * + * @param builderDto the generation DTO + * @return the rendering DTO for code generation + */ + public static GenerationTargetClassDto toRenderingDto(BuilderDefinitionDto builderDto) { + GenerationTargetClassDto renderingDto = new GenerationTargetClassDto(); + renderingDto.setTypeName(builderDto.getTypeName()); + renderingDto.setClassAccessModifier(builderDto.getClassAccessModifier()); + renderingDto.setSuperType(builderDto.getSuperType()); + renderingDto.setClassJavadoc(builderDto.getClassJavadoc()); + + // Copy class fields + builderDto.getClassFields().forEach(renderingDto::addClassField); + + // Copy constructors + builderDto.getConstructors().forEach(renderingDto::addConstructor); + + // Copy generics + builderDto.getGenerics().forEach(renderingDto::addGeneric); + + // Copy imports + builderDto.getImports().forEach(renderingDto::addImport); + + // Copy class annotations + builderDto.getClassAnnotations().forEach(renderingDto::addClassAnnotation); + + // Copy interfaces + builderDto.getInterfaces().forEach(renderingDto::addInterface); + + // Map and copy methods from fields + for (FieldDto field : builderDto.getConstructorFieldsForBuilder()) { + for (BuilderMethodDto method : field.getMethods()) { + renderingDto.addMethod(toMethodDto(method)); + } + } + for (FieldDto field : builderDto.getSetterFieldsForBuilder()) { + for (BuilderMethodDto method : field.getMethods()) { + renderingDto.addMethod(toMethodDto(method)); + } + } + + // Map and copy builder-level methods from enhancers + for (BuilderMethodDto classMethod : builderDto.getMethods()) { + renderingDto.addMethod(toMethodDto(classMethod)); + } + + // Map and copy nested types from enhancers + for (BuilderNestedTypeDto builderNestedType : builderDto.getNestedTypes()) { + renderingDto.addNestedType(toNestedTypeDto(builderNestedType)); + } + + return renderingDto; + } + + /** + * Maps a {@link BuilderMethodDto} to a {@link MethodDto}, enriching the javadoc with the + * pre-built source description if available. + * + *

All rendering-relevant fields are copied. The {@code MethodCodeDto} is shared by reference + * (not deep-copied), since the rendering phase only reads from it. + * + * @param classMethod the generation DTO to map + * @return a new {@link MethodDto} with all rendering fields copied + */ + private static 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); + } + } + method.setJavadoc(javadoc); + classMethod.getAnnotations().forEach(method::addAnnotation); + classMethod.getParameters().forEach(method::addParameter); + classMethod.getGenericParameters().forEach(method::addGenericParameter); + + // Copy method code: set code format and copy all arguments + if (classMethod.hasCode()) { + method.setCode(classMethod.getMethodCodeDto().getCodeFormat()); + for (MethodCodePlaceholder argument : classMethod.getMethodCodeDto().getCodeArguments()) { + if (argument instanceof MethodCodeStringPlaceholder stringPlaceholder) { + method.addArgument(stringPlaceholder.getLabel(), stringPlaceholder.getValue()); + } else if (argument instanceof MethodCodeTypePlaceholder typePlaceholder) { + method.addArgument(typePlaceholder.getLabel(), typePlaceholder.getValue()); + } + } + } + + return method; + } + + /** + * Maps a {@link BuilderNestedTypeDto} (generation DTO) to a {@link NestedTypeDto} (rendering + * DTO). + * + *

All rendering-relevant fields are copied, including type name, kind, visibility, javadoc, + * annotations, and methods (mapped via {@link #toMethodDto}). + * + * @param builderNestedType the generation DTO to map + * @return a new {@link NestedTypeDto} with all rendering fields copied + */ + public static NestedTypeDto toNestedTypeDto(BuilderNestedTypeDto builderNestedType) { + NestedTypeDto nestedType = new NestedTypeDto(); + nestedType.setTypeName(builderNestedType.getTypeName()); + nestedType.setKind(builderNestedType.getKind()); + nestedType.setVisibility(builderNestedType.getVisibility()); + 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/model/core/ClassFieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/ClassFieldDto.java index c465d62d..8a75a55f 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/ClassFieldDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/ClassFieldDto.java @@ -42,8 +42,7 @@ /** * Represents a field declaration in the generated class. * - *

This DTO contains only rendering information for a field. It is created from {@code FieldDto} - * during finalization in {@code BuilderDefinitionCreator}. + *

This DTO contains only rendering information for a field, consumed by the code generator. */ public class ClassFieldDto { /** Field name in generated class. */ diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java index 3f565fdb..bd83f4f5 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java @@ -25,12 +25,14 @@ package org.javahelpers.simple.builders.processor.model.core; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.GenericParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; @@ -49,7 +51,7 @@ public class FieldDto { private TypeName fieldType; /** List of all methods in builder, which provide helpers to change the field. */ - private final List fieldSetterMethodsList = new ArrayList<>(); + private final List fieldSetterMethodsList = new ArrayList<>(); /** * Original javadoc description extracted from setter or constructor parameter. This is used when @@ -151,11 +153,14 @@ public void setFieldType(TypeName fieldType) { /** * Getting list of methods to modify that field. * - * @return list of methods with type {@code - * org.javahelpers.simple.builders.internal.dtos.MethodDto} + * @return list of methods with type {@code BuilderMethodDto} */ - public List getMethods() { - return fieldSetterMethodsList; + public List getMethods() { + return Collections.unmodifiableList(fieldSetterMethodsList); + } + + public void removeMethods(Collection toRemove) { + this.fieldSetterMethodsList.removeAll(toRemove); } /** @@ -163,7 +168,7 @@ public List getMethods() { * * @param methodDto method definition */ - public void addMethod(MethodDto methodDto) { + public void addMethod(BuilderMethodDto methodDto) { this.fieldSetterMethodsList.add(methodDto); } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/GenerationTargetClassDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/GenerationTargetClassDto.java index 685cde3c..a61fc56c 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/GenerationTargetClassDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/GenerationTargetClassDto.java @@ -74,29 +74,24 @@ public class GenerationTargetClassDto { /** Imports (both regular and static imports). */ private final Set imports = new LinkedHashSet<>(); - /** - * Nested types (interfaces or classes) to be generated inside the builder, such as the "With" - * interface. - */ + /** Nested types (interfaces or classes) to be generated inside the generated class. */ private final List nestedTypes = new LinkedList<>(); /** - * Class-level annotations to be added to the generated builder class. These are added by - * BuilderEnhancers and include annotations like @Generated, @BuilderImplementation, etc. + * Class-level annotations to be added to the generated class. * *

Uses a Set to ensure annotation uniqueness and prevent duplicates. */ private final Set classAnnotations = new LinkedHashSet<>(); /** - * Interfaces to be implemented by the generated builder class. These are added by - * BuilderEnhancers and include interfaces like IBuilderBase. + * Interfaces to be implemented by the generated class. * *

Uses a Set to ensure interface uniqueness and prevent duplicates. */ private final Set interfaces = new LinkedHashSet<>(); - /** Class-level JavaDoc for the generated builder class. */ + /** Class-level JavaDoc for the generated class. */ private JavadocDto classJavadoc; public TypeName getTypeName() { @@ -222,7 +217,8 @@ public List getGenerics() { } /** - * Returns the list of nested types (interfaces or classes) to be generated inside the builder. + * Returns the list of nested types (interfaces or classes) to be generated inside the generated + * class. * * @return the list of nested types */ @@ -231,7 +227,7 @@ public List getNestedTypes() { } /** - * Adds a nested type definition to the builder. + * Adds a nested type definition to the generated class. * * @param nestedType the nested type to add */ @@ -249,7 +245,7 @@ public Set getClassAnnotations() { } /** - * Adds a class-level annotation to be generated in the builder. + * Adds a class-level annotation to be generated in the generated class. * * @param annotation the class annotation to add */ @@ -258,7 +254,7 @@ public void addClassAnnotation(AnnotationDto annotation) { } /** - * Returns the set of interfaces to be implemented by the builder. + * Returns the set of interfaces to be implemented by the generated class. * * @return the set of interfaces (unique, no duplicates) */ @@ -267,7 +263,7 @@ public Set getInterfaces() { } /** - * Adds an interface to be implemented by the builder. + * Adds an interface to be implemented by the generated class. * * @param interfaceType the interface to add */ @@ -276,7 +272,7 @@ public void addInterface(InterfaceName interfaceType) { } /** - * Returns the class-level JavaDoc for the generated builder. + * Returns the class-level JavaDoc for the generated class. * * @return the class JavaDoc, or null if not set */ @@ -285,7 +281,7 @@ public JavadocDto getClassJavadoc() { } /** - * Sets the class-level JavaDoc for the generated builder. + * Sets the class-level JavaDoc for the generated class. * * @param classJavadoc the class JavaDoc to set */ diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocCodeBlockDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocCodeBlockDto.java new file mode 100644 index 00000000..ccd8a2f8 --- /dev/null +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocCodeBlockDto.java @@ -0,0 +1,38 @@ +/* + * MIT License + * + * Copyright (c) 2026 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.model.javadoc; + +import org.javahelpers.simple.builders.processor.model.method.CodeTemplateDto; + +/** + * DTO for holding code example blocks in Javadoc. + * + *

Extends {@link CodeTemplateDto} to reuse the placeholder resolution mechanism for code + * examples that appear in Javadoc comments. This allows generators to create code examples with + * placeholders that are resolved during code generation. + */ +public class JavadocCodeBlockDto extends CodeTemplateDto { + // All functionality inherited from CodeTemplateDto +} diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java index dc21fb92..f3a60eb7 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java @@ -53,6 +53,9 @@ public class JavadocDto { /** List of Javadoc tags in the order they appear. */ private final List tags = new ArrayList<>(); + /** The single usage-example code block for this Javadoc, if present. */ + private JavadocCodeBlockDto exampleUsageCodeBlock; + /** Default constructor. */ public JavadocDto() { // Default constructor @@ -116,6 +119,24 @@ public List getTags() { return tags; } + /** + * Gets the usage-example code block. + * + * @return the usage-example code block, or {@code null} if none is present + */ + public JavadocCodeBlockDto getExampleUsageCodeBlock() { + return exampleUsageCodeBlock; + } + + /** + * Sets the usage-example code block. + * + * @param codeBlock the usage-example code block, or {@code null} to clear it + */ + public void setExampleUsageCodeBlock(JavadocCodeBlockDto codeBlock) { + this.exampleUsageCodeBlock = codeBlock; + } + /** * Adds a tag with the given name and value. * @@ -208,12 +229,46 @@ public JavadocDto addThrows(String exceptionName, String descriptionFormat, Obje } /** - * Returns whether this Javadoc has any content (description or tags). + * Appends additional text to the existing description. + * + *

If the current description is blank, the additional text becomes the description. Otherwise, + * the additional text is appended with a space separator. + * + * @param additionalText the text to append to the description + * @return this JavadocDto for fluent chaining + */ + public JavadocDto appendDescription(String additionalText) { + if (StringUtils.isNotBlank(additionalText)) { + this.description = + StringUtils.isBlank(description) ? additionalText : description + " " + additionalText; + } + return this; + } + + /** + * Appends additional text to the existing description on a new line. + * + *

If the current description is blank, the additional text becomes the description. Otherwise, + * the additional text is appended with a newline separator. + * + * @param additionalText the text to append on a new line + * @return this JavadocDto for fluent chaining + */ + public JavadocDto appendDescriptionLine(String additionalText) { + if (StringUtils.isNotBlank(additionalText)) { + this.description = + StringUtils.isBlank(description) ? additionalText : description + "\n" + additionalText; + } + return this; + } + + /** + * Returns whether this Javadoc has any content (description, tags, or an example code block). * - * @return true if there is description text or at least one tag + * @return true if there is description text, at least one tag, or an example code block */ public boolean hasContent() { - return StringUtils.isNotBlank(description) || !tags.isEmpty(); + return StringUtils.isNotBlank(description) || !tags.isEmpty() || exampleUsageCodeBlock != null; } @Override diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/BuilderMethodDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/BuilderMethodDto.java new file mode 100644 index 00000000..948c24d7 --- /dev/null +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/BuilderMethodDto.java @@ -0,0 +1,619 @@ +/* + * MIT License + * + * Copyright (c) 2026 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.model.method; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import org.apache.commons.lang3.StringUtils; +import org.javahelpers.simple.builders.core.enums.AccessModifier; +import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; +import org.javahelpers.simple.builders.processor.model.javadoc.JavadocCodeBlockDto; +import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; +import org.javahelpers.simple.builders.processor.model.type.GenericParameterDto; +import org.javahelpers.simple.builders.processor.model.type.TypeName; + +/** + * BuilderMethodDto containing all information for generating a method in the builder class, + * including field-origin metadata for pre-conflict-resolution logging and javadoc enrichment. + * + *

This is the generation-side DTO. It is produced by generators and enhancers, and mapped to + * {@link MethodDto} (the rendering DTO) by {@link + * org.javahelpers.simple.builders.processor.model.core.BuilderToGenerationTypeMapper} before being + * added to {@code GenerationTargetClassDto}. + */ +public class BuilderMethodDto { + // Priority constants for method conflict resolution (higher values win) + public static final int PRIORITY_HIGHEST = 100; // Direct setters, with() methods + public static final int PRIORITY_HIGH = 80; // Supplier, transform methods + public static final int PRIORITY_MEDIUM = 70; // Consumer, builder consumers + public static final int PRIORITY_LOW = 60; // Specialized consumers + + /** Access modifier for method. */ + private Optional modifier = Optional.empty(); + + /** Whether the method is static. */ + private boolean isStatic = false; + + /** Priority for method conflict resolution. Higher wins. */ + private int priority = 0; + + /** Ordering for method generation. Lower values appear first in generated class. */ + private int ordering = 1000; // Default high value for field-generated methods + + /** Name of method. */ + private String methodName; + + /** Return type of method. */ + private TypeName returnType; + + /** Javadoc comment for the method. */ + private JavadocDto javadoc; + + /** List of annotations on this method. */ + private final List annotations = new ArrayList<>(); + + /** List of parameters of Method. */ + private final LinkedList parameters = new LinkedList<>(); + + /** List of generic type parameters for the method (e.g., ). */ + private final List genericParameters = new ArrayList<>(); + + /** Definition of inner implementation for method. */ + private final MethodCodeDto methodCodeDto = new MethodCodeDto(); + + /** + * Fluent-chain fragment describing how this method is invoked in Javadoc examples (e.g., {@code + * .title("example value")}). When present, downstream enhancers use it to synthesise the + * method-level example block (as {@code builder;}) and to aggregate the class-level + * kitchen-sink chain. A {@code null} value means the method should not appear in either example. + */ + private String exampleChainFragment; + + /** Name of the source field this method was generated for. {@code null} for enhancer methods. */ + private String sourceFieldName; + + /** + * Pre-built javadoc origin line for this method, e.g. {@code

Generated from setter {@link + * PersonDto#setAuthor(String) setAuthor(String author)}}. {@code null} for enhancer methods. + */ + private String sourceDescription; + + /** Whether this method was generated for a constructor field (vs a setter field). */ + private boolean constructorField; + + /** Default constructor. */ + public BuilderMethodDto() { + // Default constructor + } + + /** + * Constructor with method name and return type. + * + * @param methodName the name of the method + * @param returnType the return type of the method + */ + public BuilderMethodDto(String methodName, TypeName returnType) { + this.methodName = methodName; + this.returnType = returnType; + } + + /** + * Sets the priority for this method. Higher values win when signatures clash. Priority levels: + * + *

    + *
  • {@link #PRIORITY_HIGHEST} (100): Direct setters, with() methods + *
  • {@link #PRIORITY_HIGH} (80): Supplier methods, transform methods (e.g., format, toArray) + *
  • {@link #PRIORITY_MEDIUM} (70): Consumer methods, builder consumers + *
  • {@link #PRIORITY_LOW} (60): Specialized consumers (e.g., StringBuilder) + *
  • 0: Default (no priority set) + *
+ * + * @param priority the priority value (higher values take precedence in conflicts) + */ + public void setPriority(int priority) { + this.priority = priority; + } + + /** + * Returns the priority of this method for conflict resolution. + * + * @return the priority value + */ + public int getPriority() { + return priority; + } + + /** + * Sets the ordering for this method. + * + *

Lower values appear first in the generated class. Methods with the same ordering and name + * are sorted using the following enhanced rules: + * + *

    + *
  1. Methods with fewer parameters come first + *
  2. Non-generic methods come before generic methods + *
  3. Full method signature (name(paramType1,paramType2,...)) used for final ordering + *
+ * + * @param ordering the ordering value (lower values appear first) + */ + public void setOrdering(int ordering) { + this.ordering = ordering; + } + + /** + * Returns the ordering of this method. + * + * @return the ordering value + */ + public int getOrdering() { + return ordering; + } + + /** + * Setting the inner implementation of a method. Supports placeholders which has to be set by + * addArgument. + * + * @param codeFormat Codeformat with placeholders + */ + public void setCode(String codeFormat) { + methodCodeDto.setCodeFormat(codeFormat); + } + + /** + * Adding the value for a text - placeholder. + * + * @param name name of placeholder + * @param value dynamic value of placeholder + */ + public void addArgument(String name, String value) { + methodCodeDto.addArgument(name, value); + } + + /** + * Adding the value for a type - placeholder. + * + * @param name name of placeholder + * @param value dynamic value of placeholder + */ + public void addArgument(String name, TypeName value) { + methodCodeDto.addArgument(name, value); + } + + /** + * Getter for inner implementation of method. + * + * @return {@code MethodCodeDto} containing definition of implementation + */ + public MethodCodeDto getMethodCodeDto() { + return methodCodeDto; + } + + /** + * Returns the fluent-chain fragment for Javadoc examples (e.g. {@code .title("example value")}) + * or {@code null} if this method should not participate in example generation. + * + * @return the fragment or {@code null} + */ + public String getExampleChainFragment() { + return exampleChainFragment; + } + + /** + * Stores the fluent-chain fragment describing how this method is invoked in examples. + * + *

The fragment contains just the method invocation, e.g. {@code title("example value")}. + * Downstream enhancers synthesise the method-level example block (as {@code builder.;}) + * and the class-level kitchen-sink chain from it. + * + *

Automatically adds a method-level example to the javadoc if javadoc exists and has no + * existing examples (to avoid overriding manually set examples). + * + * @param exampleChainFragment the fragment or {@code null} to clear + */ + public void setExampleChainFragment(String exampleChainFragment) { + this.exampleChainFragment = exampleChainFragment; + // Automatically add method-level example to javadoc if javadoc exists and has no examples + if (exampleChainFragment != null + && javadoc != null + && javadoc.getExampleUsageCodeBlock() == null) { + JavadocCodeBlockDto methodExample = new JavadocCodeBlockDto(); + methodExample.setCodeFormat("builder.%s;".formatted(exampleChainFragment)); + javadoc.setExampleUsageCodeBlock(methodExample); + } + } + + /** + * Checks if the method has a code block. + * + * @return true if the method has a code block, false otherwise + */ + public boolean hasCode() { + return methodCodeDto.hasCode(); + } + + /** + * Getting name of method. + * + * @return name with type {@code java.lang.String} + */ + public String getMethodName() { + return methodName; + } + + /** + * Setting name of method. + * + * @param methodName name with type {@code java.lang.String} + */ + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + /** + * Adding a further parameter of method. + * + * @param paramDto parameter to be added of type {@code + * org.javahelpers.simple.builders.internal.dtos.MethodParameterDto} + */ + public void addParameter(MethodParameterDto paramDto) { + this.parameters.add(paramDto); + } + + /** + * Getting a list of parameters of method. + * + * @return List of parameters of type {@code + * org.javahelpers.simple.builders.internal.dtos.MethodParameterDto} + */ + public List getParameters() { + return parameters; + } + + /** + * Adds a generic type parameter to this method. + * + * @param genericParameter the generic parameter to add + */ + public void addGenericParameter(GenericParameterDto genericParameter) { + this.genericParameters.add(genericParameter); + } + + /** + * Getting a list of generic type parameters of method. + * + * @return List of generic parameters of type {@code GenericParameterDto} + */ + public List getGenericParameters() { + return genericParameters; + } + + /** + * Getting the access modifier for method. Optional for usage in stream-notation. + * + * @return modifier {@code java.util.Optional} access modifier of type {@code AccessModifier} + */ + public Optional getModifier() { + return modifier; + } + + /** + * Sets the access modifier for method. + * + * @param modifier access modifier of type {@code AccessModifier} + */ + public void setModifier(AccessModifier modifier) { + this.modifier = Optional.ofNullable(modifier); + } + + /** + * Returns whether this method is static. + * + * @return true if the method is static, false otherwise + */ + public boolean isStatic() { + return isStatic; + } + + /** + * Sets whether this method is static. + * + * @param isStatic true if the method should be static, false otherwise + */ + public void setStatic(boolean isStatic) { + this.isStatic = isStatic; + } + + /** + * Gets the return type of the method. + * + * @return the return type as TypeName + */ + public TypeName getReturnType() { + return returnType; + } + + /** + * Sets the return type of the method. + * + * @param returnType the return type as TypeName + */ + public void setReturnType(TypeName returnType) { + this.returnType = returnType; + } + + /** + * Returns a unique signature key for the method based on name and parameter types. Used for + * conflict resolution. The signature matches Java's method signature rules (name + parameter + * types, ignoring generics due to type erasure). + * + * @return the signature key (e.g., "fieldName(java.lang.String,java.util.List)") + */ + public String getSignatureKey() { + StringBuilder sb = new StringBuilder(); + sb.append(methodName).append('('); + for (int i = 0; i < parameters.size(); i++) { + if (i > 0) sb.append(','); + TypeName tn = parameters.get(i).getParameterType(); + // Handle null package names + if (StringUtils.isNoneBlank(tn.getPackageName())) { + sb.append(tn.getPackageName()).append('.'); + } + sb.append(tn.getClassName()); + } + sb.append(')'); + return sb.toString(); + } + + public JavadocDto getJavadoc() { + return javadoc; + } + + /** + * Sets the Javadoc comment for the method. + * + * @param javadoc the Javadoc comment + */ + public void setJavadoc(JavadocDto javadoc) { + this.javadoc = javadoc; + } + + /** + * Returns the list of annotations on this method. + * + * @return list of annotations + */ + public List getAnnotations() { + return annotations; + } + + /** + * Adds an annotation to this method. + * + * @param annotation the annotation to add + */ + public void addAnnotation(AnnotationDto annotation) { + this.annotations.add(annotation); + } + + /** + * Returns the name of the source field this method was generated for. + * + * @return the source field name, or {@code null} for enhancer-generated methods + */ + public String getSourceFieldName() { + return sourceFieldName; + } + + /** + * Sets the name of the source field this method was generated for. + * + * @param sourceFieldName the source field name, or {@code null} for enhancer-generated methods + */ + public void setSourceFieldName(String sourceFieldName) { + this.sourceFieldName = sourceFieldName; + } + + /** + * Gets the pre-built javadoc origin line for this method. + * + * @return the source description line, or {@code null} for enhancer-generated methods + */ + public String getSourceDescription() { + return sourceDescription; + } + + /** + * Sets the pre-built javadoc origin line for this method. + * + * @param sourceDescription the full origin line, e.g. {@code

Generated from setter {@link + * PersonDto#setAuthor(String) setAuthor(String author)}} + */ + public void setSourceDescription(String sourceDescription) { + this.sourceDescription = sourceDescription; + } + + /** + * Returns whether this method was generated for a constructor field. + * + * @return true if this method was generated for a constructor field, false otherwise + */ + public boolean isConstructorField() { + return constructorField; + } + + /** + * Sets whether this method was generated for a constructor field. + * + * @param constructorField true if this method was generated for a constructor field + */ + public void setConstructorField(boolean constructorField) { + this.constructorField = constructorField; + } + + /** + * Comparator for sorting BuilderMethodDto instances with sophisticated ordering rules. + * + *

Sorting order: + * + *

    + *
  1. Priority (descending — higher priority first) + *
  2. Ordering value (ascending — lower ordering first) + *
  3. Method name + *
  4. Methods with fewer parameters come first + *
  5. Non-generic methods come before generic methods + *
  6. Full method signature (name(paramType1,paramType2,...)) used for final ordering + *
+ */ + public static class BuilderMethodComparator implements java.util.Comparator { + + @Override + public int compare(BuilderMethodDto m1, BuilderMethodDto m2) { + // Primary sort: priority (descending — higher priority wins) + int priorityCompare = Integer.compare(m2.getPriority(), m1.getPriority()); + if (priorityCompare != 0) { + return priorityCompare; + } + + // Secondary sort: ordering value + int orderingCompare = Integer.compare(m1.getOrdering(), m2.getOrdering()); + if (orderingCompare != 0) { + return orderingCompare; + } + + // Tertiary sort: method name + int nameCompare = m1.getMethodName().compareTo(m2.getMethodName()); + if (nameCompare != 0) { + return nameCompare; + } + + // Quaternary sort: parameter count (fewer parameters first) + int paramCountCompare = Integer.compare(m1.getParameters().size(), m2.getParameters().size()); + if (paramCountCompare != 0) { + return paramCountCompare; + } + + // Quinary sort: generic vs non-generic (non-generic first) + boolean m1Generic = hasGenericParameters(m1); + boolean m2Generic = hasGenericParameters(m2); + if (m1Generic != m2Generic) { + return m1Generic ? 1 : -1; // non-generic comes first + } + + // Final sort: full method signature + String signature1 = createMethodSignature(m1); + String signature2 = createMethodSignature(m2); + return signature1.compareTo(signature2); + } + + /** + * Creates a qualified name string for a TypeName. + * + * @param typeName the type name + * @return qualified name using the type's own formatting logic + */ + private String getQualifiedName(TypeName typeName) { + return typeName.getFullQualifiedName(); + } + + /** + * Creates a method signature string for sorting purposes. + * + *

The signature includes method name and parameter types in the format: + * methodName(paramType1,paramType2,...) + * + * @param method the method to create signature for + * @return signature string for comparison + */ + private String createMethodSignature(BuilderMethodDto method) { + StringBuilder signature = new StringBuilder(method.getMethodName()); + signature.append("("); + + java.util.List paramTypes = + method.getParameters().stream() + .map(param -> getQualifiedName(param.getParameterType())) + .toList(); + + signature.append(String.join(",", paramTypes)); + signature.append(")"); + + return signature.toString(); + } + + /** + * Checks if a method has generic parameters. + * + * @param method the method to check + * @return true if any parameter is generic (contains type parameters) + */ + private boolean hasGenericParameters(BuilderMethodDto method) { + return method.getParameters().stream() + .anyMatch(param -> getQualifiedName(param.getParameterType()).contains("<")); + } + } + + /** + * Returns a string representation of this method in Java method signature format. + * + *

Examples: + * + *

    + *
  • {@code public PersonBuilder name(String)} + *
  • {@code public PersonBuilder age(int)} + *
  • {@code public PersonBuilder tags(Consumer>)} + *
+ * + * @return method signature as a string + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + // Add modifier if present + modifier.ifPresent(m -> sb.append(m.toString().toLowerCase(Locale.ROOT)).append(" ")); + + // Add static if applicable + if (isStatic) { + sb.append("static "); + } + + // Add return type (void if not specified) + String returnTypeName = returnType != null ? returnType.getClassName() : "void"; + sb.append(returnTypeName).append(" "); + + // Add method name and parameters + String parameterList = + parameters.stream() + .map(param -> param.getParameterType().getClassName()) + .collect(java.util.stream.Collectors.joining(", ")); + + sb.append(methodName).append("(").append(parameterList).append(")"); + + return sb.toString(); + } +} diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/CodeTemplateDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/CodeTemplateDto.java new file mode 100644 index 00000000..9424149a --- /dev/null +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/CodeTemplateDto.java @@ -0,0 +1,204 @@ +/* + * MIT License + * + * Copyright (c) 2026 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.model.method; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.javahelpers.simple.builders.processor.model.imports.ImportStatement; +import org.javahelpers.simple.builders.processor.model.imports.RegularImport; +import org.javahelpers.simple.builders.processor.model.imports.StaticImport; +import org.javahelpers.simple.builders.processor.model.type.TypeName; +import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; +import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; + +/** + * Base DTO for holding code template information with placeholders. + * + *

This class provides the common infrastructure for code templates that use placeholders (e.g., + * {@code $label:N}, {@code $label:L}, {@code $label:S}, {@code $label:T}) which are resolved during + * code generation. + */ +public class CodeTemplateDto { + /** Format of code. Holding placeholder for dynamic values. */ + private String codeFormat; + + /** List of placeholders in CodeFormat. Containing dynamic values too. */ + private final List> codeArguments = new ArrayList<>(); + + /** Types used in the code body that aren't covered by arguments. */ + private final Set codeBlockImports = new LinkedHashSet<>(); + + /** + * Setting format of code. + * + * @param codeFormat Codeformat + */ + public void setCodeFormat(String codeFormat) { + this.codeFormat = codeFormat; + } + + /** + * Adding an argument for Codeformat. Helperfunction to set text value. + * + * @param name name in codeformat + * @param value value to fill in codeformat + */ + public void addArgument(String name, String value) { + codeArguments.add(new MethodCodeStringPlaceholder(name, value)); + } + + /** + * Adding an argument for Codeformat. Helperfunction to set TypeName value. + * + * @param name name in codeformat + * @param value value to fill in codeformat + */ + public void addArgument(String name, TypeName value) { + codeArguments.add(new MethodCodeTypePlaceholder(name, value)); + addCodeBlockImport(value); + } + + /** + * Getter for Codeformat. + * + * @return codeformat + */ + public String getCodeFormat() { + return codeFormat; + } + + /** + * Getter for arguments in Codeformat. + * + * @return argument values + */ + @SuppressWarnings("java:S1452") + public List> getCodeArguments() { + return Collections.unmodifiableList(codeArguments); + } + + public void addCodeArgument(MethodCodePlaceholder placeholder) { + codeArguments.add(placeholder); + } + + /** + * Checks if this code DTO has code content. + * + * @return true if code format is not null and not blank + */ + public boolean hasCode() { + return !StringUtils.isBlank(codeFormat); + } + + /** + * Returns the set of imports used in the code body that aren't covered by arguments. + * + * @return set of imports used in code body + */ + public Set getCodeBlockImports() { + return codeBlockImports; + } + + /** + * Adding an import for a type used in the code block. + * + * @param typeName type to import + */ + public void addCodeBlockImport(TypeName typeName) { + if (typeName instanceof TypeNameGeneric generic) { + addCodeBlockImport(generic.getRawType()); + generic.getInnerTypeArguments().forEach(this::addCodeBlockImport); + } else if (typeName instanceof TypeNameArray array) { + addCodeBlockImport(array.getTypeOfArray()); + } else { + this.codeBlockImports.add(new RegularImport(typeName)); + } + } + + /** + * Adding an import for a type used in the code block (convenience overload accepting Class). + * + * @param clazz the class to import + */ + public void addCodeBlockImport(Class clazz) { + addCodeBlockImport(TypeName.of(clazz)); + } + + /** + * Adds a static import for a method/field used in the code block (convenience method). + * + * @param clazz the class containing the static member + * @param memberName the name of the static member + */ + public void addStaticImport(Class clazz, String memberName) { + this.codeBlockImports.add(new StaticImport(TypeName.of(clazz), memberName)); + } + + /** + * Adds type imports for a TypeName and its generic type arguments (convenience method). + * + * @param type the type to add imports for + */ + public void addTypeImports(TypeName type) { + if (type == null) { + return; + } + + // Add the main type + addCodeBlockImport(type); + + // Add generic type arguments recursively + if (type instanceof TypeNameGeneric genericType) { + genericType.getInnerTypeArguments().forEach(this::addTypeImports); + } + } + + /** + * Appends additional code to the existing code format with string formatting support. + * + *

This method concatenates the provided formatted code string to the current code format, + * separated by a newline for proper formatting. This is useful for building up method bodies + * incrementally, especially when constructing complex code with multiple sections. + * + *

Supports the same formatting syntax as {@link String#format(String, Object...)}, allowing + * for dynamic value insertion using placeholders like %s, %d, etc. + * + * @param formatted the code fragment with format placeholders to append to the existing code + * format + * @param args the arguments to be formatted into the string + */ + public void append(String formatted, Object... args) { + String formattedCode = args.length > 0 ? String.format(formatted, args) : formatted; + if (StringUtils.isEmpty(codeFormat)) { + codeFormat = formattedCode; + } else { + codeFormat += "\n" + formattedCode; + } + } +} diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodCodeDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodCodeDto.java index d4d26099..71c75c4a 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodCodeDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodCodeDto.java @@ -24,170 +24,7 @@ package org.javahelpers.simple.builders.processor.model.method; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import org.apache.commons.lang3.StringUtils; -import org.javahelpers.simple.builders.processor.model.imports.ImportStatement; -import org.javahelpers.simple.builders.processor.model.imports.RegularImport; -import org.javahelpers.simple.builders.processor.model.imports.StaticImport; -import org.javahelpers.simple.builders.processor.model.type.TypeName; -import org.javahelpers.simple.builders.processor.model.type.TypeNameArray; -import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; - /** DTO for holding information of code implementation. */ -public class MethodCodeDto { - /** Format of code. Holding placeholder for dynamic values. */ - private String codeFormat; - - /** List of placeholders in CodeFormat. Containing dynamic values too. */ - private final List> codeArguments = new ArrayList<>(); - - /** Types used in the code body that aren't covered by arguments. */ - private final Set codeBlockImports = new LinkedHashSet<>(); - - /** - * Setting format of code. - * - * @param codeFormat Codeformat - */ - public void setCodeFormat(String codeFormat) { - this.codeFormat = codeFormat; - } - - /** - * Adding an argument for Codeformat. Helperfunction to set text value. - * - * @param name name in codeformat - * @param value value to fill in codeformat - */ - public void addArgument(String name, String value) { - codeArguments.add(new MethodCodeStringPlaceholder(name, value)); - } - - /** - * Adding an argument for Codeformat. Helperfunction to set TypeName value. - * - * @param name name in codeformat - * @param value value to fill in codeformat - */ - public void addArgument(String name, TypeName value) { - codeArguments.add(new MethodCodeTypePlaceholder(name, value)); - addCodeBlockImport(value); - } - - /** - * Getter for Codeformat. - * - * @return codeformat - */ - public String getCodeFormat() { - return codeFormat; - } - - /** - * Getter for arguments in Codeformat. - * - * @return argument values - */ - @SuppressWarnings("java:S1452") - public List> getCodeArguments() { - return codeArguments; - } - - /** - * Checks if this code DTO has code content. - * - * @return true if code format is not null and not blank - */ - public boolean hasCode() { - return !StringUtils.isBlank(codeFormat); - } - - /** - * Returns the set of imports used in the code body that aren't covered by arguments. - * - * @return set of imports used in code body - */ - public Set getCodeBlockImports() { - return codeBlockImports; - } - - /** - * Adding an import for a type used in the code block. - * - * @param typeName type to import - */ - public void addCodeBlockImport(TypeName typeName) { - if (typeName instanceof TypeNameGeneric generic) { - addCodeBlockImport(generic.getRawType()); - generic.getInnerTypeArguments().forEach(this::addCodeBlockImport); - } else if (typeName instanceof TypeNameArray array) { - addCodeBlockImport(array.getTypeOfArray()); - } else { - this.codeBlockImports.add(new RegularImport(typeName)); - } - } - - /** - * Adding an import for a type used in the code block (convenience overload accepting Class). - * - * @param clazz the class to import - */ - public void addCodeBlockImport(Class clazz) { - addCodeBlockImport(TypeName.of(clazz)); - } - - /** - * Adds a static import for a method/field used in the code block (convenience method). - * - * @param clazz the class containing the static member - * @param memberName the name of the static member - */ - public void addStaticImport(Class clazz, String memberName) { - this.codeBlockImports.add(new StaticImport(TypeName.of(clazz), memberName)); - } - - /** - * Adds type imports for a TypeName and its generic type arguments (convenience method). - * - * @param type the type to add imports for - */ - public void addTypeImports(TypeName type) { - if (type == null) { - return; - } - - // Add the main type - addCodeBlockImport(type); - - // Add generic type arguments recursively - if (type instanceof TypeNameGeneric genericType) { - genericType.getInnerTypeArguments().forEach(this::addTypeImports); - } - } - - /** - * Appends additional code to the existing code format with string formatting support. - * - *

This method concatenates the provided formatted code string to the current code format, - * separated by a newline for proper formatting. This is useful for building up method bodies - * incrementally, especially when constructing complex code with multiple sections. - * - *

Supports the same formatting syntax as {@link String#format(String, Object...)}, allowing - * for dynamic value insertion using placeholders like %s, %d, etc. - * - * @param formatted the code fragment with format placeholders to append to the existing code - * format - * @param args the arguments to be formatted into the string - */ - public void append(String formatted, Object... args) { - String formattedCode = args.length > 0 ? String.format(formatted, args) : formatted; - if (StringUtils.isEmpty(codeFormat)) { - codeFormat = formattedCode; - } else { - codeFormat += "\n" + formattedCode; - } - } +public class MethodCodeDto extends CodeTemplateDto { + // All functionality inherited from CodeTemplateDto } diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java index 896986f9..e9d3a845 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.core.enums.AccessModifier; @@ -35,25 +36,22 @@ import org.javahelpers.simple.builders.processor.model.type.GenericParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; -/** MethodDto containing all information to generate a method in builder. */ +/** + * Rendering-side DTO containing all information to generate a method in a generated class. + * + *

This DTO is consumed by the code generator. It is generation-specific in the sense that it + * holds only rendering-relevant data — no field-origin metadata or other generation-phase-only + * information. + */ public class MethodDto { - // Priority constants for method conflict resolution (higher values win) - public static final int PRIORITY_HIGHEST = 100; // Direct setters, with() methods - public static final int PRIORITY_HIGH = 80; // Supplier, transform methods - public static final int PRIORITY_MEDIUM = 70; // Consumer, builder consumers - public static final int PRIORITY_LOW = 60; // Specialized consumers - /** Access modifier for method. */ private Optional modifier = Optional.empty(); /** Whether the method is static. */ private boolean isStatic = false; - /** Priority for method conflict resolution. Higher wins. */ - private int priority = 0; - /** Ordering for method generation. Lower values appear first in generated class. */ - private int ordering = 1000; // Default high value for field-generated methods + private int ordering = 1000; /** Name of method. */ private String methodName; @@ -92,32 +90,6 @@ public MethodDto(String methodName, TypeName returnType) { this.returnType = returnType; } - /** - * Sets the priority for this method. Higher values win when signatures clash. Priority levels: - * - *

    - *
  • {@link #PRIORITY_HIGHEST} (100): Direct setters, with() methods - *
  • {@link #PRIORITY_HIGH} (80): Supplier methods, transform methods (e.g., format, toArray) - *
  • {@link #PRIORITY_MEDIUM} (70): Consumer methods, builder consumers - *
  • {@link #PRIORITY_LOW} (60): Specialized consumers (e.g., StringBuilder) - *
  • 0: Default (no priority set) - *
- * - * @param priority the priority value (higher values take precedence in conflicts) - */ - public void setPriority(int priority) { - this.priority = priority; - } - - /** - * Returns the priority of this method for conflict resolution. - * - * @return the priority value - */ - public int getPriority() { - return priority; - } - /** * Sets the ordering for this method. * @@ -185,9 +157,9 @@ public MethodCodeDto getMethodCodeDto() { } /** - * Checks if the constructor has a code block. + * Checks if the method has a code block. * - * @return true if the constructor has a code block, false otherwise + * @return true if the method has a code block, false otherwise */ public boolean hasCode() { return methodCodeDto.hasCode(); @@ -202,15 +174,6 @@ public String getMethodName() { return methodName; } - /** - * Helper function to generate a name for setter method. Does not work on non-field methods. - * - * @return returning name of field-setter method - */ - public String createFieldSetterMethodName() { - return "set" + StringUtils.capitalize(this.getMethodName()); - } - /** * Setting name of method. * @@ -369,7 +332,7 @@ public void addAnnotation(AnnotationDto annotation) { /** * Comparator for sorting MethodDto instances with sophisticated ordering rules. * - *

Sorting order for methods with same priority and name: + *

Sorting order for methods with same ordering and name: * *

    *
  1. Methods with fewer parameters come first @@ -464,9 +427,9 @@ private boolean hasGenericParameters(MethodDto method) { *

    Examples: * *

      - *
    • {@code public PersonBuilder name(String)} - *
    • {@code public PersonBuilder age(int)} - *
    • {@code public PersonBuilder tags(Consumer>)} + *
    • {@code public GeneratedClass name(String)} + *
    • {@code public GeneratedClass age(int)} + *
    • {@code public GeneratedClass tags(Consumer>)} *
    * * @return method signature as a string @@ -476,7 +439,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); // Add modifier if present - modifier.ifPresent(m -> sb.append(m.toString().toLowerCase()).append(" ")); + modifier.ifPresent(m -> sb.append(m.toString().toLowerCase(Locale.ROOT)).append(" ")); // Add static if applicable if (isStatic) { diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/BuilderNestedTypeDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/BuilderNestedTypeDto.java new file mode 100644 index 00000000..e8493f0a --- /dev/null +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/BuilderNestedTypeDto.java @@ -0,0 +1,113 @@ +/* + * MIT License + * + * Copyright (c) 2026 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.model.type; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import org.javahelpers.simple.builders.core.enums.AccessModifier; +import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; +import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; + +/** + * Generation-side DTO for a nested type (interface or class) to be generated inside the builder. + * + *

    This is the generation-phase counterpart of {@link NestedTypeDto}. It holds {@link + * BuilderMethodDto} instances (generation-side method DTOs) and is produced by enhancers. During + * finalization in {@code BuilderDefinitionCreator}, it is mapped to {@link NestedTypeDto} (the + * rendering DTO) via {@link + * org.javahelpers.simple.builders.processor.model.core.BuilderToGenerationTypeMapper#toNestedTypeDto}. + * + *

    For example, the "With" interface that allows DTOs to implement fluent modification methods. + */ +public class BuilderNestedTypeDto { + + /** The simple name of the nested type (e.g., "With"). */ + private String typeName; + + /** The kind of nested type (INTERFACE or CLASS). */ + private NestedTypeDto.NestedTypeKind kind; + + /** Visibility of this nested type. */ + private AccessModifier visibility = AccessModifier.PUBLIC; + + /** Methods to be generated in this nested type (generation-side). */ + private final List methods = new LinkedList<>(); + + /** Javadoc comment for this nested type. */ + private JavadocDto javadoc; + + /** Type-level annotations for this nested type. */ + private final List annotations = new ArrayList<>(); + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public NestedTypeDto.NestedTypeKind getKind() { + return kind; + } + + public void setKind(NestedTypeDto.NestedTypeKind kind) { + this.kind = kind; + } + + public AccessModifier getVisibility() { + return visibility; + } + + public void setVisibility(AccessModifier visibility) { + this.visibility = visibility; + } + + public List getMethods() { + return methods; + } + + public void addMethod(BuilderMethodDto method) { + this.methods.add(method); + } + + public JavadocDto getJavadoc() { + return javadoc; + } + + public void setJavadoc(JavadocDto javadoc) { + this.javadoc = javadoc; + } + + public List getAnnotations() { + return annotations; + } + + public void addAnnotation(AnnotationDto annotation) { + this.annotations.add(annotation); + } +} diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/NestedTypeDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/NestedTypeDto.java index 1f6f6b50..38809cf1 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/NestedTypeDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/NestedTypeDto.java @@ -33,7 +33,11 @@ import org.javahelpers.simple.builders.processor.model.method.MethodDto; /** - * Represents a nested type (interface or class) to be generated inside the builder. + * Rendering-side DTO for a nested type (interface or class) to be generated inside a generated + * class. + * + *

    It holds {@link MethodDto} instances (rendering-side method DTOs) and is consumed by the code + * generator. * *

    For example, the "With" interface that allows DTOs to implement fluent modification methods. */ diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeName.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeName.java index 3102acf2..ec8719e8 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeName.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeName.java @@ -81,6 +81,19 @@ public String getClassName() { return className; } + /** + * Returns the simple name with generic type arguments (if any), without package prefixes. + * + *

    For a plain {@code TypeName} this is equivalent to {@link #getClassName()}. Subclasses like + * {@link TypeNameGeneric} override this to include generic parameters, e.g. {@code List} + * instead of just {@code List}. + * + * @return simple name with generics, e.g. {@code String} or {@code List} + */ + public String getSimpleNameWithGenerics() { + return getClassName(); + } + /** * Returns the full qualified name (package + class name). * diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeNameGeneric.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeNameGeneric.java index 115d6160..505aabde 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeNameGeneric.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/type/TypeNameGeneric.java @@ -166,6 +166,22 @@ public String getFullQualifiedName() { return baseName + "<" + typeArgs + ">"; } + @Override + public String getSimpleNameWithGenerics() { + String baseName = getClassName(); + if (innerTypeArguments.isEmpty()) { + return baseName; + } + + String typeArgs = + innerTypeArguments.stream() + .map(TypeName::getSimpleNameWithGenerics) + .reduce((a, b) -> a + ", " + b) + .orElse(""); + + return baseName + "<" + typeArgs + ">"; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java index 7f1b6dc9..377edc4b 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java @@ -29,12 +29,15 @@ import static org.javahelpers.simple.builders.processor.analysis.JavaLangMapper.map2MethodParameter; import static org.javahelpers.simple.builders.processor.processing.AnnotationValidator.validateAnnotatedElement; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; import org.apache.commons.lang3.StringUtils; @@ -52,7 +55,7 @@ import org.javahelpers.simple.builders.processor.model.core.ClassFieldDto; import org.javahelpers.simple.builders.processor.model.core.FieldDto; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; -import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric; @@ -115,8 +118,10 @@ public static BuilderDefinitionDto extractFromElement( * *

      *
    • Converts FieldDto instances to ClassFieldDto instances - *
    • Collects all methods from fields and core methods - *
    • Sets source descriptions on methods for conflict resolution logging + *
    • Sets origin info (sourceFieldName, constructorField) on each BuilderMethodDto + *
    • Performs pre-conflict resolution at BuilderMethodDto level with origin logging + *
    • Maps all BuilderMethodDto to MethodDto via BuilderToGenerationTypeMapper + *
    • Collects all mapped methods from fields and class-level enhancer methods *
    • Sets class access modifier *
    • Sets static imports for TrackedValue *
    @@ -134,33 +139,250 @@ private static void finalizeDefinition( builderDto.addClassField(classField); } - // 2. Collect methods from fields - for (FieldDto field : builderDto.getConstructorFieldsForBuilder()) { - for (MethodDto method : field.getMethods()) { - builderDto.addMethod(method); - } - } - for (FieldDto field : builderDto.getSetterFieldsForBuilder()) { - for (MethodDto method : field.getMethods()) { - builderDto.addMethod(method); - } - } + // 2. Set origin info on BuilderMethodDto for javadoc enrichment + setConstructorOriginInfo(builderDto); + addSetterOriginInfo(builderDto); + + // 3. Builder-specific conflict resolution: resolve by priority, log with field origin + resolveMethodConflicts(builderDto, context); - // 3. Set class access modifier + // 4. Set class access modifier builderDto.setClassAccessModifier(builderDto.getConfiguration().getBuilderAccess()); - // 4. Set static imports for TrackedValue + // 5. Set static imports for TrackedValue builderDto.addStaticImport(TrackedValue.class, "changedValue"); builderDto.addStaticImport(TrackedValue.class, "initialValue"); builderDto.addStaticImport(TrackedValue.class, "unsetValue"); context.debugEndOperation( - "Finalized: %d class fields, %d methods, %d constructors", + "Finalized: %d class fields, %d builder-level methods, %d constructors", builderDto.getClassFields().size(), builderDto.getMethods().size(), builderDto.getConstructors().size()); } + /** + * Sets origin info (source field name, source description, constructor flag) on all {@link + * BuilderMethodDto}s associated with constructor fields. + * + *

    For constructor parameters, the full constructor signature is built once and shared across + * all constructor-field methods. The origin line includes an {@code {@link}} tag with types-only + * link target and full display label. + * + * @param builderDto the builder definition containing constructor fields + */ + private static void setConstructorOriginInfo(BuilderDefinitionDto builderDto) { + String sourceDescription = buildConstructorSourceDescription(builderDto); + for (FieldDto field : builderDto.getConstructorFieldsForBuilder()) { + for (BuilderMethodDto method : field.getMethods()) { + method.setSourceFieldName(field.getOriginalFieldName()); + method.setSourceDescription(sourceDescription); + method.setConstructorField(true); + } + } + } + + /** + * Sets origin info (source field name, source description, constructor flag) on all {@link + * BuilderMethodDto}s associated with setter fields. + * + *

    For each setter field, the signature is built per-field and the origin line includes an + * {@code {@link}} tag with types-only link target and full display label. + * + * @param builderDto the builder definition containing setter fields + */ + private static void addSetterOriginInfo(BuilderDefinitionDto builderDto) { + String sourceClassName = + builderDto.getBuildingTargetTypeName() != null + ? builderDto.getBuildingTargetTypeName().getClassName() + : null; + for (FieldDto field : builderDto.getSetterFieldsForBuilder()) { + String sourceDescription = buildSetterSourceDescription(field, sourceClassName); + for (BuilderMethodDto method : field.getMethods()) { + method.setSourceFieldName(field.getOriginalFieldName()); + method.setSourceDescription(sourceDescription); + method.setConstructorField(false); + } + } + } + + /** + * Builds the javadoc origin line for constructor-based methods. + * + *

    The full constructor signature is built from all constructor fields, e.g. {@code + *

    Generated from parameter in constructor {@link PersonDto#PersonDto(String, int) + * PersonDto(String name, int age)}}. + * + * @param builderDto the builder definition containing constructor fields and target type name + * @return the complete origin line, or {@code null} if no constructor fields or target type + */ + private static String buildConstructorSourceDescription(BuilderDefinitionDto builderDto) { + if (builderDto.getConstructorFieldsForBuilder().isEmpty() + || builderDto.getBuildingTargetTypeName() == null) { + return null; + } + String className = builderDto.getBuildingTargetTypeName().getClassName(); + String displayParams = + builderDto.getConstructorFieldsForBuilder().stream() + .map( + f -> + "%s %s" + .formatted( + f.getFieldType().getSimpleNameWithGenerics(), f.getOriginalFieldName())) + .collect(Collectors.joining(", ")); + String linkParams = + builderDto.getConstructorFieldsForBuilder().stream() + .map(f -> f.getFieldType().getClassName()) + .collect(Collectors.joining(", ")); + return "

    Generated from parameter in constructor {@link %s#%s(%s) %s(%s)}" + .formatted(className, className, linkParams, className, displayParams); + } + + /** + * Builds the javadoc origin line for setter-based methods. + * + *

    The signature is built from the field's setter name and type, e.g. {@code

    Generated from + * setter {@link PersonDto#setAuthor(String) setAuthor(String author)}}. + * + * @param field the setter field to build the description for + * @param sourceClassName the simple class name of the source DTO, or {@code null} if unknown + * @return the complete origin line + */ + private static String buildSetterSourceDescription(FieldDto field, String sourceClassName) { + String displaySignature = + "%s(%s %s)" + .formatted( + field.getSetterName(), + field.getFieldType().getSimpleNameWithGenerics(), + field.getOriginalFieldName()); + String linkSignature = + "%s(%s)".formatted(field.getSetterName(), field.getFieldType().getClassName()); + if (sourceClassName != null) { + return "

    Generated from setter {@link %s#%s %s}" + .formatted(sourceClassName, linkSignature, displaySignature); + } else { + return "

    Generated from setter %s".formatted(displaySignature); + } + } + + /** + * Builder-specific conflict resolution at the BuilderMethodDto level, using priority-based + * resolution with field-origin logging. + * + *

    This is the primary place for conflict resolution because only here the field-origin + * metadata ({@code sourceFieldName}, {@code constructorField}) is available. Methods with the + * same signature key are resolved by keeping the highest-priority one; losers are removed from + * their field/class method lists so they are never mapped to the rendering DTO. + * + *

    A generic safety net in {@link + * org.javahelpers.simple.builders.processor.classgen.roaster.RoasterCodeGenerator#resolveMethodConflicts} + * handles any remaining duplicates (e.g., from enhancer methods added after this step) by keeping + * the first occurrence. + * + * @param builderDto the builder definition containing all methods + * @param context the processing context for logging + */ + private static void resolveMethodConflicts( + BuilderDefinitionDto builderDto, ProcessingContext context) { + context.debugStartOperation("Resolving method conflicts"); + + // Collect all BuilderMethodDto instances grouped by signature key + Map> methodsBySignature = collectMethodsBySignature(builderDto); + + // Resolve conflicts: keep highest priority, remove losers + Set methodsToRemove = new HashSet<>(); + for (Map.Entry> entry : methodsBySignature.entrySet()) { + List methodsWithSameSignature = entry.getValue(); + boolean isConflicting = methodsWithSameSignature.size() > 1; + if (isConflicting) { + // Sort by priority (descending), then ordering, then name, etc. for deterministic winner + methodsWithSameSignature.sort(new BuilderMethodDto.BuilderMethodComparator()); + BuilderMethodDto winner = methodsWithSameSignature.get(0); + context.warning( + "Method conflict resolved for signature '%s': %d methods found. " + + "Kept: priority=%d, sourceField='%s', constructorField=%b. " + + "Dropped: %s", + entry.getKey(), + methodsWithSameSignature.size(), + winner.getPriority(), + winner.getSourceFieldName(), + winner.isConstructorField(), + methodsWithSameSignature.stream() + .skip(1) + .map( + m -> + String.format( + "[priority=%d, sourceField='%s', constructorField=%b]", + m.getPriority(), m.getSourceFieldName(), m.isConstructorField())) + .toList()); + // Mark losers for removal + methodsWithSameSignature.stream().skip(1).forEach(methodsToRemove::add); + } + } + + // Remove losing methods from their field/class method lists + removeMethodsFromBuilder(builderDto, methodsToRemove); + + context.debugEndOperation( + "Resolved: %d signatures, %d conflicts, %d methods removed", + methodsBySignature.size(), + methodsBySignature.values().stream().filter(l -> l.size() > 1).count(), + methodsToRemove.size()); + } + + /** + * Collects all {@link BuilderMethodDto} instances from the builder definition, grouped by their + * signature key. This includes methods from constructor fields, setter fields, and builder-level + * enhancer methods. + * + * @param builderDto the builder definition containing all methods + * @return map from signature key to list of methods with that signature + */ + private static Map> collectMethodsBySignature( + BuilderDefinitionDto builderDto) { + Map> methodsBySignature = new HashMap<>(); + + for (FieldDto field : builderDto.getConstructorFieldsForBuilder()) { + for (BuilderMethodDto method : field.getMethods()) { + methodsBySignature + .computeIfAbsent(method.getSignatureKey(), k -> new ArrayList<>()) + .add(method); + } + } + for (FieldDto field : builderDto.getSetterFieldsForBuilder()) { + for (BuilderMethodDto method : field.getMethods()) { + methodsBySignature + .computeIfAbsent(method.getSignatureKey(), k -> new ArrayList<>()) + .add(method); + } + } + for (BuilderMethodDto classMethod : builderDto.getMethods()) { + methodsBySignature + .computeIfAbsent(classMethod.getSignatureKey(), k -> new ArrayList<>()) + .add(classMethod); + } + + return methodsBySignature; + } + + /** + * Removes the given methods from all field method lists and builder-level methods in the builder + * definition. + * + * @param builderDto the builder definition containing all methods + * @param methodsToRemove the set of methods to remove + */ + private static void removeMethodsFromBuilder( + BuilderDefinitionDto builderDto, Set methodsToRemove) { + for (FieldDto field : builderDto.getConstructorFieldsForBuilder()) { + field.removeMethods(methodsToRemove); + } + for (FieldDto field : builderDto.getSetterFieldsForBuilder()) { + field.removeMethods(methodsToRemove); + } + builderDto.removeMethods(methodsToRemove); + } + /** * Converts a FieldDto to a ClassFieldDto for rendering. * @@ -579,7 +801,7 @@ private static Optional createFieldDto( // Builder and constructor information is now set when TypeName is created in JavaLangMapper // Use GeneratorRegistry to generate all methods for this field - List generatedMethods = + List generatedMethods = context.getGeneratorRegistry().generateAllMethods(field, dtoType, builderType); generatedMethods.forEach(field::addMethod); diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java index f88df074..80ddcc7c 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java @@ -474,6 +474,15 @@ public class 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 = PersonDtoMinimalBuilder.create()
    +        *     .withName("example value")
    +        *     .withTags(List.of("example value"))
    +        *     .build();
    +        * }
    */ public class PersonDtoMinimalBuilder implements IBuilderBase { @@ -505,6 +514,12 @@ public PersonDtoMinimalBuilder(PersonDto instance) { /** * Creating a new builder for {@code test.PersonDto}. * + *

    Example:

    + * + *
    {@code
    +           * PersonDtoMinimalBuilder builder = PersonDtoMinimalBuilder.create();
    +           * }
    + * * @return builder for {@code test.PersonDto} */ public static PersonDtoMinimalBuilder create() { @@ -513,6 +528,14 @@ public static PersonDtoMinimalBuilder create() { /** * Sets the value for name. + *

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

    Example:

    + * + *
    {@code
    +           * builder.withName("example value");
    +           * }
    * * @param name name * @return current instance of builder @@ -524,6 +547,14 @@ public PersonDtoMinimalBuilder withName(String name) { /** * Sets the value for tags. + *

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

    Example:

    + * + *
    {@code
    +           * builder.withTags(List.of("example value"));
    +           * }
    * * @param tags tags * @return current instance of builder @@ -535,6 +566,12 @@ public PersonDtoMinimalBuilder withTags(List tags) { /** * Builds the configured DTO instance. + * + *

    Example:

    + * + *
    {@code
    +           * PersonDto result = builder.build();
    +           * }
    */ @Override public PersonDto build() { diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderJavadocExampleTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderJavadocExampleTest.java new file mode 100644 index 00000000..b32f82f9 --- /dev/null +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderJavadocExampleTest.java @@ -0,0 +1,427 @@ +/* + * MIT License + * + * Copyright (c) 2026 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 org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertGenerationSucceeded; +import static org.javahelpers.simple.builders.processor.testing.ProcessorTestUtils.loadGeneratedSource; + +import com.google.testing.compile.Compilation; +import java.util.stream.Stream; +import javax.tools.JavaFileObject; +import org.javahelpers.simple.builders.processor.testing.ProcessorAsserts; +import org.javahelpers.simple.builders.processor.testing.ProcessorTestUtils; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests that verify the field-specific code examples added to the generated builder javadoc. + * + *

    Two levels are covered: + * + *

      + *
    • Class-level javadoc: a single "kitchen-sink" chain that invokes every generated method + * (basic setter + supplier + collection helpers + list consumer + ...) on sample values so + * users see the full feature catalogue at a glance. + *
    • Method-level javadoc: each generated method has its own, method-specific example block + * showing exactly how to invoke that single method. + *
    + * + *

    Assertions use exact javadoc text blocks (whitespace-normalized) so both the presence and the + * relative ordering of lines is verified in a single substring match. + */ +class BuilderJavadocExampleTest { + + protected Compilation compile(JavaFileObject... sourceFiles) { + return ProcessorTestUtils.createCompiler().compile(sourceFiles); + } + + @Test + void shouldGenerateClassJavadocExampleWithKitchenSinkChain() { + // Given: a DTO with fields covering basic setter, supplier and list helpers + String packageName = "test"; + String className = "BookDto"; + String builderClassName = className + "Builder"; + + JavaFileObject dto = + ProcessorTestUtils.simpleBuilderClass( + packageName, + className, + """ + private String title; + private int pages; + private java.util.List tags; + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public int getPages() { return pages; } + public void setPages(int pages) { this.pages = pages; } + public java.util.List getTags() { return tags; } + public void setTags(java.util.List tags) { this.tags = tags; } + """); + + // When + Compilation compilation = compile(dto); + + // Then + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + + // The generated class javadoc must contain the full kitchen-sink chain, + // with fields in alphabetical order (pages, tags, title) and within each + // field the generator lines in priority order. + ProcessorAsserts.assertContaining( + generatedCode, + """ + *

    {@code
    +        * BookDto result = BookDtoBuilder.create()
    +        *     .pages(42)
    +        *     .pages(() -> 42)
    +        *     .tags(List.of("example value"))
    +        *     .tags(() -> List.of("example value"))
    +        *     .tags(t -> t.add("example value"))
    +        *     .tags("example value", "example value")
    +        *     .add2Tags("example value")
    +        *     .title("example value")
    +        *     .title("Hello %s", "World")
    +        *     .title(() -> "example value")
    +        *     .title(sb -> sb.append("text"))
    +        *     .build();
    +        * }
    + """); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("methodJavadocExampleCases") + void shouldGenerateMethodJavadocExample(String className, String source, String expectedJavadoc) { + String builderClassName = className + "Builder"; + JavaFileObject dto = ProcessorTestUtils.simpleBuilderClass("test", className, source); + + Compilation compilation = compile(dto); + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + ProcessorAsserts.assertContaining(generatedCode, expectedJavadoc); + } + + private static Stream methodJavadocExampleCases() { + return Stream.of( + Arguments.of( + Named.of("basic string setter", "Person"), + """ + private String teamname; + public String getTeamname() { return teamname; } + public void setTeamname(String teamname) { this.teamname = teamname; } + """, + """ + * Sets the value for teamname. + *

    + * Generated from setter {@link Person#setTeamname(String) setTeamname(String teamname)} + * + *

    Example:

    + * + *
    {@code
    +            * builder.teamname("example value");
    +            * }
    + * + * @param teamname teamname + * @return current instance of builder + */ + public PersonBuilder teamname(String teamname) + """), + Arguments.of( + Named.of("primitive setter", "Counter"), + """ + private int amount; + public int getAmount() { return amount; } + public void setAmount(int amount) { this.amount = amount; } + """, + """ + * Sets the value for amount. + *

    + * Generated from setter {@link Counter#setAmount(int) setAmount(int amount)} + * + *

    Example:

    + * + *
    {@code
    +            * builder.amount(42);
    +            * }
    + * + * @param amount amount + * @return current instance of builder + */ + public CounterBuilder amount(int amount) + """), + Arguments.of( + Named.of("supplier", "SupplierDto"), + """ + private String title; + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + """, + """ + *

    Example:

    + * + *
    {@code
    +            * builder.title(() -> "example value");
    +            * }
    + """), + Arguments.of( + Named.of("add to collection", "TagsDto"), + """ + private java.util.List tags; + public java.util.List getTags() { return tags; } + public void setTags(java.util.List tags) { this.tags = tags; } + """, + """ + * Adds a single element to tags. + *

    + * Generated from setter {@link TagsDto#setTags(List) setTags(List tags)} + * + *

    Example:

    + * + *
    {@code
    +            * builder.add2Tags("example value");
    +            * }
    + * + * @param element the element to add + * @return current instance of builder + */ + public TagsDtoBuilder add2Tags(String element) + """), + Arguments.of( + Named.of("list consumer", "ListDto"), + """ + private java.util.List tags; + public java.util.List getTags() { return tags; } + public void setTags(java.util.List tags) { this.tags = tags; } + """, + """ + *

    Example:

    + * + *
    {@code
    +            * builder.tags(t -> t.add("example value"));
    +            * }
    + """), + Arguments.of( + Named.of("create", "CreateDto"), + """ + private String name; + public String getName() { return name; } + public void setName(String name) { this.name = name; } + """, + """ + *

    Example:

    + * + *
    {@code
    +            * CreateDtoBuilder builder = CreateDtoBuilder.create();
    +            * }
    + """), + Arguments.of( + Named.of("build", "BuildDto"), + """ + private String name; + public String getName() { return name; } + public void setName(String name) { this.name = name; } + """, + """ + *

    Example:

    + * + *
    {@code
    +            * BuildDto result = builder.build();
    +            * }
    + """), + Arguments.of( + Named.of("map setter", "MapDto"), + """ + private java.util.Map values; + public java.util.Map getValues() { return values; } + public void setValues(java.util.Map values) { this.values = values; } + """, + """ + *

    Example:

    + * + *
    {@code
    +            * builder.values(Map.of("example value", 42));
    +            * }
    + """), + Arguments.of( + Named.of("integer-key map setter", "IntegerKeyMapDto"), + """ + private java.util.Map values; + public java.util.Map getValues() { return values; } + public void setValues(java.util.Map values) { this.values = values; } + """, + """ + *

    Example:

    + * + *
    {@code
    +            * builder.values(Map.of(42, "example value"));
    +            * }
    + """), + Arguments.of( + Named.of("optional setter", "OptionalDto"), + """ + private java.util.Optional subtitle; + public java.util.Optional getSubtitle() { return subtitle; } + public void setSubtitle(java.util.Optional subtitle) { this.subtitle = subtitle; } + """, + """ + *

    Example:

    + * + *
    {@code
    +            * builder.subtitle(Optional.of("example value"));
    +            * }
    + """)); + } + + // --------------------------------------------------------------------------- + // Negative cases: no example emitted when placeholder value is unresolvable + // and empty code blocks must not be rendered at all. + // --------------------------------------------------------------------------- + + @Test + void shouldOmitExampleBlockWhenFieldTypeHasNoDefaultValue() { + // Given: field with a reference type that has no entry in JavadocExampleValues + // and no @SimpleBuilder on the helper type -> no example value available. + String packageName = "test"; + String className = "UnknownTypeDto"; + String builderClassName = className + "Builder"; + + JavaFileObject dto = + ProcessorTestUtils.simpleBuilderClass( + packageName, + className, + """ + private HelperPlain helper; + public HelperPlain getHelper() { return helper; } + public void setHelper(HelperPlain helper) { this.helper = helper; } + """); + + JavaFileObject helper = + ProcessorTestUtils.forSource( + """ + package test; + public class HelperPlain { public HelperPlain() {} } + """); + + Compilation compilation = compile(dto, helper); + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + + // The basic setter must still be generated, + // but the method javadoc must NOT contain any example block: + // neither a bogus "builder.helper(null)" line + // nor an empty "
    {@code ... }
    " block. + ProcessorAsserts.assertContaining(generatedCode, "public UnknownTypeDtoBuilder helper("); + ProcessorAsserts.assertNotContaining( + generatedCode, + // no example line falling back to `null` + "builder.helper(null)", + // no empty example block either + "
    {@code\n}
    ", + "
    {@code }
    ", + "
    {@code}
    "); + } + + @Test + void shouldOmitClassExampleLineForUnresolvableField() { + // Given: a DTO with both a resolvable field and an unresolvable field. + // The class-level kitchen-sink chain must include ONLY the resolvable field. + String packageName = "test"; + String className = "MixedDto"; + String builderClassName = className + "Builder"; + + JavaFileObject dto = + ProcessorTestUtils.simpleBuilderClass( + packageName, + className, + """ + private String title; + private HelperPlain helper; + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public HelperPlain getHelper() { return helper; } + public void setHelper(HelperPlain helper) { this.helper = helper; } + """); + + JavaFileObject helper = + ProcessorTestUtils.forSource( + """ + package test; + public class HelperPlain { public HelperPlain(String arg) {} } + """); + + Compilation compilation = compile(dto, helper); + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + + // The class-level kitchen-sink chain includes ONLY the resolvable field (title). + // The helper field (HelperPlain) has no example value and must be omitted. + // HelperPlain has only a parameterized constructor (no empty constructor) and no builder. + ProcessorAsserts.assertContaining( + generatedCode, + """ + *
    {@code
    +        * MixedDto result = MixedDtoBuilder.create()
    +        *     .title("example value")
    +        *     .title("Hello %s", "World")
    +        *     .title(() -> "example value")
    +        *     .title(sb -> sb.append("text"))
    +        *     .build();
    +        * }
    + """); + + // No `.helper(...)` call in the class example chain + ProcessorAsserts.assertNotContaining(generatedCode, ".helper("); + } + + @Test + void shouldNotRenderEmptyJavadocExampleBlocks() { + // Sanity guard: no generated method should ever contain an empty example block. + String packageName = "test"; + String className = "NoEmptyDto"; + String builderClassName = className + "Builder"; + + JavaFileObject dto = + ProcessorTestUtils.simpleBuilderClass( + packageName, + className, + """ + private String name; + public String getName() { return name; } + public void setName(String name) { this.name = name; } + """); + + Compilation compilation = compile(dto); + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + + // Degenerate empty blocks must never appear. + ProcessorAsserts.assertNotContaining( + generatedCode, "
    {@code\n}
    ", "
    {@code }
    ", "
    {@code}
    "); + } +} 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 026fc763..27b6fde2 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 @@ -113,16 +113,18 @@ void shouldLogDebugMessagesWhenVerboseModeEnabled() { "[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] │ │ ├─ Applying: ClassJavaDocEnhancer (priority: 10)", "[DEBUG] │ │ └─ Applied 8 builder enhancers", "[DEBUG] │ ├─ Finalizing builder definition", - "[DEBUG] │ │ └─ Finalized: 1 class fields, 9 methods, 2 constructors", + "[DEBUG] │ │ ├─ Resolving method conflicts", + "[DEBUG] │ │ │ └─ Resolved: 9 signatures, 0 conflicts, 0 methods removed", + "[DEBUG] │ │ └─ Finalized: 1 class fields, 5 builder-level methods, 2 constructors", "[DEBUG] │ ├─ Builder will be generated as: VerboseTestBuilder", "[DEBUG] │ └─ Builder definition extracted: VerboseTestBuilder", "[DEBUG] ├─ Code generation for class: VerboseTestBuilder", 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 4cd832b2..6615be7f 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 @@ -147,7 +147,6 @@ public PersonDto(String name, int age, Optional email, 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.ArrayList; import java.util.HashSet; import java.util.LinkedList; @@ -176,6 +175,46 @@ public PersonDto(String name, int age, Optional email, * 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")
    +         *     .name("Hello %s", "World")
    +         *     .name(() -> "example value")
    +         *     .name(sb -> sb.append("text"))
    +         *     .age(42)
    +         *     .age(() -> 42)
    +         *     .email(Optional.of("example value"))
    +         *     .email("Hello %s", "World")
    +         *     .email("example value")
    +         *     .email(() -> Optional.of("example value"))
    +         *     .email(sb -> sb.append("text"))
    +         *     .nicknames(List.of("example value"))
    +         *     .nicknames(() -> List.of("example value"))
    +         *     .nicknames(t -> t.add("example value"))
    +         *     .nicknames("example value", "example value")
    +         *     .add2Nicknames("example value")
    +         *     .tags(Set.of("example value"))
    +         *     .tags(() -> Set.of("example value"))
    +         *     .tags(t -> t.add("example value"))
    +         *     .tags("example value", "example value")
    +         *     .add2Tags("example value")
    +         *     .metadata(Map.of("example value", "example value"))
    +         *     .metadata(() -> Map.of("example value", "example value"))
    +         *     .metadata(Map.entry("key", "example value"))
    +         *     .address(AddressDtoBuilder.create().build())
    +         *     .address(() -> AddressDtoBuilder.create().build())
    +         *     .address(addressDtoBuilder -> addressDtoBuilder)
    +         *     .previousAddresses(t -> t.add(addressDtoBuilder -> addressDtoBuilder))
    +         *     .phoneNumbers(List.of("example value"))
    +         *     .phoneNumbers(() -> List.of("example value"))
    +         *     .phoneNumbers(t -> t.add("example value"))
    +         *     .phoneNumbers("example value", "example value")
    +         *     .add2PhoneNumbers("example value")
    +         *     .build();
    +         * }
    */ @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor") @BuilderImplementation(forClass = PersonDto.class) @@ -185,42 +224,34 @@ public class PersonDtoBuilder implements IBuilderBase { * Tracked value for name: name. */ private TrackedValue name = unsetValue(); - /** * Tracked value for age: age. */ private TrackedValue age = unsetValue(); - /** * Tracked value for email: email. */ private TrackedValue> email = unsetValue(); - /** * Tracked value for nicknames: nicknames. */ private TrackedValue> nicknames = unsetValue(); - /** * Tracked value for tags: tags. */ private TrackedValue> tags = unsetValue(); - /** * Tracked value for metadata: metadata. */ private TrackedValue> metadata = unsetValue(); - /** * Tracked value for address: address. */ private TrackedValue address = unsetValue(); - /** * Tracked value for previousAddresses: previousAddresses. */ private TrackedValue> previousAddresses = unsetValue(); - /** * Tracked value for phoneNumbers: phoneNumbers. */ @@ -252,6 +283,12 @@ public PersonDtoBuilder(PersonDto instance) { /** * Creating a new builder for {@code test.PersonDto}. * + *

    Example:

    + * + *
    {@code
    +           * PersonDtoBuilder builder = PersonDtoBuilder.create();
    +           * }
    + * * @return builder for {@code test.PersonDto} */ public static PersonDtoBuilder create() { @@ -260,6 +297,17 @@ public static PersonDtoBuilder create() { /** * Adds a single element to nicknames. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.add2Nicknames("example value");
    +           * }
    * * @param element the element to add * @return current instance of builder @@ -278,6 +326,17 @@ public PersonDtoBuilder add2Nicknames(String element) { /** * Adds a single element to phoneNumbers. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.add2PhoneNumbers("example value");
    +           * }
    * * @param element the element to add * @return current instance of builder @@ -296,6 +355,11 @@ public PersonDtoBuilder add2PhoneNumbers(String element) { /** * Adds a single element to previousAddresses. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * * @param element the element to add * @return current instance of builder @@ -314,6 +378,17 @@ public PersonDtoBuilder add2PreviousAddresses(AddressDto element) { /** * Adds a single element to tags. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.add2Tags("example value");
    +           * }
    * * @param element the element to add * @return current instance of builder @@ -332,6 +407,17 @@ public PersonDtoBuilder add2Tags(String element) { /** * Sets the value for address. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.address(AddressDtoBuilder.create().build());
    +           * }
    * * @param address address * @return current instance of builder @@ -343,6 +429,17 @@ public PersonDtoBuilder address(AddressDto address) { /** * Sets the value for address using a builder consumer that produces the value. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.address(addressDtoBuilder -> addressDtoBuilder);
    +           * }
    * * @param addressBuilderConsumer consumer providing an instance of a builder for address * @return current instance of builder @@ -358,6 +455,17 @@ public PersonDtoBuilder address(Consumer addressBuilderConsum /** * Sets the value for address by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.address(() -> AddressDtoBuilder.create().build());
    +           * }
    * * @param addressSupplier supplier for address * @return current instance of builder @@ -369,6 +477,17 @@ public PersonDtoBuilder address(Supplier addressSupplier) { /** * Sets the value for age. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.age(42);
    +           * }
    * * @param age age * @return current instance of builder @@ -380,6 +499,17 @@ public PersonDtoBuilder age(int age) { /** * Sets the value for age by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.age(() -> 42);
    +           * }
    * * @param ageSupplier supplier for age * @return current instance of builder @@ -391,6 +521,17 @@ public PersonDtoBuilder age(Supplier ageSupplier) { /** * Sets the value for email. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.email("example value");
    +           * }
    * * @param email email * @return current instance of builder @@ -402,6 +543,17 @@ public PersonDtoBuilder email(String email) { /** * Sets the value for email. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.email(Optional.of("example value"));
    +           * }
    * * @param email email * @return current instance of builder @@ -413,6 +565,17 @@ public PersonDtoBuilder email(Optional email) { /** * Sets the value for email by executing the provided consumer. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.email(sb -> sb.append("text"));
    +           * }
    * * @param emailStringBuilderConsumer consumer providing an instance of email * @return current instance of builder @@ -426,6 +589,17 @@ public PersonDtoBuilder email(Consumer emailStringBuilderConsumer /** * Sets the value for email by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.email(() -> Optional.of("example value"));
    +           * }
    * * @param emailSupplier supplier for email * @return current instance of builder @@ -438,6 +612,17 @@ public PersonDtoBuilder email(Supplier> emailSupplier) { /** * Sets the String value for email by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.email("Hello %s", "World");
    +           * }
    * * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -450,6 +635,17 @@ public PersonDtoBuilder email(String format, Object... args) { /** * Sets the value for metadata. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.metadata(Map.entry("key", "example value"));
    +           * }
    * * @param metadata metadata * @return current instance of builder @@ -461,6 +657,17 @@ public PersonDtoBuilder metadata(Entry... metadata) { /** * Sets the value for metadata. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.metadata(Map.of("example value", "example value"));
    +           * }
    * * @param metadata metadata * @return current instance of builder @@ -472,6 +679,11 @@ public PersonDtoBuilder metadata(Map metadata) { /** * Sets the value for metadata using a builder consumer that produces the value. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * * @param metadataBuilderConsumer consumer providing an instance of a builder for metadata * @return current instance of builder @@ -487,6 +699,17 @@ public PersonDtoBuilder metadata(Consumer> metada /** * Sets the value for metadata by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.metadata(() -> Map.of("example value", "example value"));
    +           * }
    * * @param metadataSupplier supplier for metadata * @return current instance of builder @@ -498,6 +721,17 @@ public PersonDtoBuilder metadata(Supplier> metadataSupplier) /** * Sets the value for name. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.name("example value");
    +           * }
    * * @param name name * @return current instance of builder @@ -509,6 +743,17 @@ public PersonDtoBuilder name(String name) { /** * Sets the value for name by executing the provided consumer. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.name(sb -> sb.append("text"));
    +           * }
    * * @param nameStringBuilderConsumer consumer providing an instance of name * @return current instance of builder @@ -522,6 +767,17 @@ public PersonDtoBuilder name(Consumer nameStringBuilderConsumer) /** * Sets the value for name by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.name(() -> "example value");
    +           * }
    * * @param nameSupplier supplier for name * @return current instance of builder @@ -534,6 +790,17 @@ public PersonDtoBuilder name(Supplier nameSupplier) { /** * Sets the String value for name by using String.format(format, args). See * {@link String#format(String, Object...)} for details. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.name("Hello %s", "World");
    +           * }
    * * @param format A format string * @param args Arguments referenced by the format specifiers in the format string. @@ -546,6 +813,17 @@ public PersonDtoBuilder name(String format, Object... args) { /** * Sets the value for nicknames. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.nicknames("example value", "example value");
    +           * }
    * * @param nicknames nicknames * @return current instance of builder @@ -557,6 +835,17 @@ public PersonDtoBuilder nicknames(String... nicknames) { /** * Sets the value for nicknames. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.nicknames(List.of("example value"));
    +           * }
    * * @param nicknames nicknames * @return current instance of builder @@ -568,6 +857,17 @@ public PersonDtoBuilder nicknames(List nicknames) { /** * Sets the value for nicknames using a builder consumer that produces the value. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.nicknames(t -> t.add("example value"));
    +           * }
    * * @param nicknamesBuilderConsumer consumer providing an instance of a builder for nicknames * @return current instance of builder @@ -583,6 +883,17 @@ public PersonDtoBuilder nicknames(Consumer> nicknamesBu /** * Sets the value for nicknames by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.nicknames(() -> List.of("example value"));
    +           * }
    * * @param nicknamesSupplier supplier for nicknames * @return current instance of builder @@ -594,6 +905,17 @@ public PersonDtoBuilder nicknames(Supplier> nicknamesSupplier) { /** * Sets the value for phoneNumbers. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.phoneNumbers("example value", "example value");
    +           * }
    * * @param phoneNumbers phoneNumbers * @return current instance of builder @@ -605,6 +927,17 @@ public PersonDtoBuilder phoneNumbers(String... phoneNumbers) { /** * Sets the value for phoneNumbers. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.phoneNumbers(List.of("example value"));
    +           * }
    * * @param phoneNumbers phoneNumbers * @return current instance of builder @@ -616,6 +949,17 @@ public PersonDtoBuilder phoneNumbers(LinkedList phoneNumbers) { /** * Sets the value for phoneNumbers using a builder consumer that produces the value. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.phoneNumbers(t -> t.add("example value"));
    +           * }
    * * @param phoneNumbersBuilderConsumer consumer providing an instance of a builder for phoneNumbers * @return current instance of builder @@ -631,6 +975,17 @@ public PersonDtoBuilder phoneNumbers(Consumer> phoneNum /** * Sets the value for phoneNumbers by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.phoneNumbers(() -> List.of("example value"));
    +           * }
    * * @param phoneNumbersSupplier supplier for phoneNumbers * @return current instance of builder @@ -642,6 +997,11 @@ public PersonDtoBuilder phoneNumbers(Supplier> phoneNumbersSu /** * Sets the value for previousAddresses. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * * @param previousAddresses previousAddresses * @return current instance of builder @@ -653,6 +1013,11 @@ public PersonDtoBuilder previousAddresses(AddressDto... previousAddresses) { /** * Sets the value for previousAddresses. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * * @param previousAddresses previousAddresses * @return current instance of builder @@ -664,6 +1029,17 @@ public PersonDtoBuilder previousAddresses(List previousAddresses) { /** * Sets the value for previousAddresses using a builder consumer that produces the value. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.previousAddresses(t -> t.add(addressDtoBuilder -> addressDtoBuilder));
    +           * }
    * * @param previousAddressesBuilderConsumer consumer providing an instance of a builder for previousAddresses * @return current instance of builder @@ -681,6 +1057,11 @@ public PersonDtoBuilder previousAddresses( /** * Sets the value for previousAddresses by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * * @param previousAddressesSupplier supplier for previousAddresses * @return current instance of builder @@ -692,6 +1073,17 @@ public PersonDtoBuilder previousAddresses(Supplier> previousAdd /** * Sets the value for tags. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.tags("example value", "example value");
    +           * }
    * * @param tags tags * @return current instance of builder @@ -703,6 +1095,17 @@ public PersonDtoBuilder tags(String... tags) { /** * Sets the value for tags. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.tags(Set.of("example value"));
    +           * }
    * * @param tags tags * @return current instance of builder @@ -714,6 +1117,17 @@ public PersonDtoBuilder tags(Set tags) { /** * Sets the value for tags using a builder consumer that produces the value. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.tags(t -> t.add("example value"));
    +           * }
    * * @param tagsBuilderConsumer consumer providing an instance of a builder for tags * @return current instance of builder @@ -729,6 +1143,17 @@ public PersonDtoBuilder tags(Consumer> tagsBuilderConsume /** * Sets the value for tags by invoking the provided supplier. + *

    + * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

    Example:

    + * + *
    {@code
    +           * builder.tags(() -> Set.of("example value"));
    +           * }
    * * @param tagsSupplier supplier for tags * @return current instance of builder @@ -769,6 +1194,12 @@ public PersonDtoBuilder conditional(BooleanSupplier condition, ConsumerExample: + * + *
    {@code
    +           * PersonDto result = builder.build();
    +           * }
    */ @Override public PersonDto build() { diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomizingDocumentationTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomizingDocumentationTest.java index 91781cbb..063461d0 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomizingDocumentationTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomizingDocumentationTest.java @@ -96,7 +96,7 @@ void testGeneratorInterfaceStructure() { import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; import java.util.List; @@ -109,7 +109,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { return List.of(); } @@ -181,7 +181,7 @@ void testCustomGeneratorMethodCreation() { import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -194,11 +194,11 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { String fieldName = field.getFieldNameInBuilder(); String methodName = "custom" + capitalize(fieldName); - MethodDto method = new MethodDto(methodName, builderType); + BuilderMethodDto method = new BuilderMethodDto(methodName, builderType); MethodParameterDto parameter = new MethodParameterDto(); parameter.setParameterName("value"); @@ -242,7 +242,7 @@ void testGeneratorPriorityPattern() { import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; import java.util.List; @@ -254,7 +254,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { return List.of(); } @@ -285,7 +285,7 @@ void testErrorHandlingPattern() { import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; import java.util.List; @@ -297,7 +297,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { try { // Generation logic return List.of(); @@ -334,7 +334,7 @@ void testConditionalApplicationPattern() { import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; import java.util.List; @@ -352,7 +352,7 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { return List.of(); } @@ -388,7 +388,7 @@ void testCompleteCustomValidationGenerator() { import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -407,11 +407,11 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { String fieldName = field.getFieldNameInBuilder(); String methodName = "validated" + capitalize(fieldName); - MethodDto method = new MethodDto(methodName, builderType); + BuilderMethodDto method = new BuilderMethodDto(methodName, builderType); String parameterName = fieldName; MethodParameterDto parameter = new MethodParameterDto(); @@ -464,7 +464,7 @@ void testCompleteCustomValidationEnhancer() { import org.javahelpers.simple.builders.processor.generators.BuilderEnhancer; import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -485,7 +485,7 @@ public boolean appliesTo(BuilderDefinitionDto builderDto, TypeName dtoType, Proc @Override public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) { // Add validation method to builder - MethodDto validateMethod = createValidateMethod(); + BuilderMethodDto validateMethod = createValidateMethod(); builderDto.addMethod(validateMethod); // Add @Valid annotation if available @@ -504,9 +504,9 @@ public int getPriority() { return 500; } - private MethodDto createValidateMethod() { + private BuilderMethodDto createValidateMethod() { TypeName returnType = new TypeName("java.lang", "Void"); - MethodDto method = new MethodDto("validate", returnType); + BuilderMethodDto method = new BuilderMethodDto("validate", returnType); method.setCode("// Validation logic here"); return method; } @@ -573,7 +573,7 @@ void testDateParserGeneratorExample() { import org.javahelpers.simple.builders.processor.generators.MethodGenerator; import org.javahelpers.simple.builders.processor.model.core.FieldDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -588,11 +588,11 @@ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext con } @Override - public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { + public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) { String fieldName = field.getFieldNameInBuilder(); String methodName = fieldName + "FromString"; - MethodDto method = new MethodDto(methodName, builderType); + BuilderMethodDto method = new BuilderMethodDto(methodName, builderType); String parameterName = fieldName + "String"; MethodParameterDto parameter = new MethodParameterDto(); @@ -637,7 +637,7 @@ void testBuilderFactoryEnhancerExample() { import org.javahelpers.simple.builders.processor.generators.BuilderEnhancer; import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto; - import org.javahelpers.simple.builders.processor.model.method.MethodDto; + import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; @@ -654,7 +654,7 @@ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext co TypeName builderType = builderDto.getBuilderTypeName(); // Add static factory method (conceptual - actual API may differ) - MethodDto factoryMethod = new MethodDto("from", builderType); + BuilderMethodDto factoryMethod = new BuilderMethodDto("from", builderType); factoryMethod.setStatic(true); MethodParameterDto parameter = new MethodParameterDto(); 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 9ccb1665..415e201a 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 @@ -389,10 +389,10 @@ public void setValue(java.util.function.Supplier valueSupplier) { .filter(d -> d.getKind() == Diagnostic.Kind.WARNING) .filter( d -> - d.getMessage(null).contains("Method conflict") - && d.getMessage(null).contains("dropped in favor of")) - .filter(d -> d.getMessage(null).contains("priority 80")) - .filter(d -> d.getMessage(null).contains("priority 100")) + d.getMessage(null).contains("Method conflict resolved") + && d.getMessage(null).contains("Dropped")) + .filter(d -> d.getMessage(null).contains("priority=80")) + .filter(d -> d.getMessage(null).contains("priority=100")) .count(); assert lowerPriorityDroppedCount > 0 diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java index 63eac689..5a01ba85 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java @@ -77,7 +77,7 @@ void shouldWrapRenderingRuntimeExceptionInBuilderException() { MethodCodeDto code = new MethodCodeDto(); code.setCodeFormat("return $value:X;"); - code.getCodeArguments().add(new UnsupportedPlaceholder("value", "irrelevant")); + code.addCodeArgument(new UnsupportedPlaceholder("value", "irrelevant")); ConstructorDto constructor = new ConstructorDto(); constructor.setVisibility(AccessModifier.PUBLIC);