Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,16 @@
*/
OptionState generateUnboxedOptional() default OptionState.UNSET;

/**
* Copy type annotations from the DTO fields to the builder fields/methods. <br>
* Useful for validation annotations (e.g. @NotNull, @Size) or other metadata that should be
* preserved.
*
* <p>Default: ENABLED <br>
* Compiler option: -Asimplebuilder.copyTypeAnnotations
*/
OptionState copyTypeAnnotations() default OptionState.UNSET;

/**
* Generate helper methods with a ArrayListBuilder supplier for lists instead of simple
* supplier, which would not allow to use in a chanined way: <br>
Expand Down
20 changes: 20 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,26 @@ PersonDto person = PersonDtoBuilder.create()

---

#### `copyTypeAnnotations`

**Default**: `ENABLED` | **Compiler Option**: `-Asimplebuilder.copyTypeAnnotations=ENABLED|DISABLED`

Copies type annotations (TYPE_USE) from the DTO fields to the builder fields and methods. This is useful for validation annotations (e.g. `@NotNull`, `@Size`) or other metadata that should be preserved.

**When ENABLED**:
```java
// DTO
private List<@NotNull String> items;

// Generated Builder
private TrackedValue<List<@NotNull String>> items;
public Builder items(List<@NotNull String> items) { ... }
```

**When DISABLED**: Type annotations are stripped from the builder.

---

### Collection Helpers

#### `usingArrayListBuilder`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

/**
* DTO representing an annotation to be copied from the target class field to the builder class
Expand Down Expand Up @@ -79,4 +81,27 @@ public Map<String, String> getMembers() {
public void addMember(String name, String value) {
this.members.put(name, value);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (o == null || getClass() != o.getClass()) {
return false;
}

AnnotationDto that = (AnnotationDto) o;

return new EqualsBuilder()
.append(annotationType, that.annotationType)
.append(members, that.members)
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(annotationType).append(members).toHashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public record BuilderConfiguration(
OptionState generateStringFormatHelpers,
OptionState generateAddToCollectionHelpers,
OptionState generateUnboxedOptional,
OptionState copyTypeAnnotations,
OptionState usingArrayListBuilder,
OptionState usingArrayListBuilderWithElementBuilders,
OptionState usingHashSetBuilder,
Expand All @@ -101,6 +102,7 @@ public record BuilderConfiguration(
.generateStringFormatHelpers(ENABLED)
.generateAddToCollectionHelpers(ENABLED)
.generateUnboxedOptional(ENABLED)
.copyTypeAnnotations(ENABLED)
.usingArrayListBuilder(ENABLED)
.usingArrayListBuilderWithElementBuilders(ENABLED)
.usingHashSetBuilder(ENABLED)
Expand Down Expand Up @@ -171,6 +173,10 @@ public boolean shouldGenerateUnboxedOptional() {
return generateUnboxedOptional == ENABLED;
}

public boolean shouldCopyTypeAnnotations() {
return copyTypeAnnotations == ENABLED;
}

public boolean shouldUseGeneratedAnnotation() {
return usingGeneratedAnnotation == ENABLED;
}
Expand Down Expand Up @@ -239,6 +245,7 @@ public BuilderConfiguration merge(BuilderConfiguration other) {
other.generateAddToCollectionHelpers, this.generateAddToCollectionHelpers))
.generateUnboxedOptional(
mergeOptionState(other.generateUnboxedOptional, this.generateUnboxedOptional))
.copyTypeAnnotations(mergeOptionState(other.copyTypeAnnotations, this.copyTypeAnnotations))
.usingArrayListBuilder(
mergeOptionState(other.usingArrayListBuilder, this.usingArrayListBuilder))
.usingArrayListBuilderWithElementBuilders(
Expand Down Expand Up @@ -328,6 +335,12 @@ public String toString() {
if (generateVarArgsHelpers != UNSET) {
builder.append("generateVarArgsHelpers", generateVarArgsHelpers);
}
if (generateUnboxedOptional != UNSET) {
builder.append("generateUnboxedOptional", generateUnboxedOptional);
}
if (copyTypeAnnotations != UNSET) {
builder.append("copyTypeAnnotations", copyTypeAnnotations);
}
if (usingArrayListBuilder != UNSET) {
builder.append("usingArrayListBuilder", usingArrayListBuilder);
}
Expand Down Expand Up @@ -379,6 +392,7 @@ public static class Builder {
private OptionState generateStringFormatHelpers = OptionState.UNSET;
private OptionState generateAddToCollectionHelpers = OptionState.UNSET;
private OptionState generateUnboxedOptional = OptionState.UNSET;
private OptionState copyTypeAnnotations = OptionState.UNSET;
private OptionState usingArrayListBuilder = OptionState.UNSET;
private OptionState usingArrayListBuilderWithElementBuilders = OptionState.UNSET;
private OptionState usingHashSetBuilder = OptionState.UNSET;
Expand Down Expand Up @@ -488,6 +502,16 @@ public Builder generateUnboxedOptional(boolean value) {
return this;
}

public Builder copyTypeAnnotations(OptionState value) {
this.copyTypeAnnotations = value;
return this;
}

public Builder copyTypeAnnotations(boolean value) {
this.copyTypeAnnotations = value ? ENABLED : DISABLED;
return this;
}

public Builder usingArrayListBuilder(OptionState value) {
this.usingArrayListBuilder = value;
return this;
Expand Down Expand Up @@ -621,6 +645,7 @@ public BuilderConfiguration build() {
generateStringFormatHelpers,
generateAddToCollectionHelpers,
generateUnboxedOptional,
copyTypeAnnotations,
usingArrayListBuilder,
usingArrayListBuilderWithElementBuilders,
usingHashSetBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import static java.util.Objects.requireNonNull;

import java.util.Optional;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

/**
* TypeName is the unambiguously definition of a type. Holding name of class and package. Could be
Expand All @@ -39,6 +41,9 @@ public class TypeName {
/** Name of class. */
private final String className;

/** Annotations on this type (TYPE_USE). */
private final java.util.List<AnnotationDto> annotations = new java.util.ArrayList<>();

/**
* Constructor for TypeName.
*
Expand Down Expand Up @@ -69,6 +74,24 @@ public String getClassName() {
return className;
}

/**
* Returns the list of annotations on this type.
*
* @return list of annotations
*/
public java.util.List<AnnotationDto> getAnnotations() {
return annotations;
}

/**
* Adds an annotation to this type.
*
* @param annotation the annotation to add
*/
public void addAnnotation(AnnotationDto annotation) {
this.annotations.add(annotation);
}

/**
* Helper function to hold a specific inner type.Is empty if this is a class without generic parts
* or a class has multiple generics.
Expand All @@ -89,4 +112,27 @@ public Optional<TypeName> getInnerType() {
public static TypeName of(Class<?> clazz) {
return new TypeName(clazz.getPackage().getName(), clazz.getSimpleName());
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (o == null || getClass() != o.getClass()) {
return false;
}

TypeName typeName = (TypeName) o;

return new EqualsBuilder()
.append(packageName, typeName.packageName)
.append(className, typeName.className)
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(packageName).append(className).toHashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class TypeNamePrimitive extends TypeName {
*
* @param primitiveType primitive enum type
*/
protected TypeNamePrimitive(PrimitiveTypeEnum primitiveType) {
public TypeNamePrimitive(PrimitiveTypeEnum primitiveType) {
super("", primitiveType.name().toLowerCase());
this.type = primitiveType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public enum CompilerArgumentsEnum {
/** Option for unboxed optional generation. */
GENERATE_UNBOXED_OPTIONAL("generateUnboxedOptional"),

/** Option for copying type annotations. */
COPY_TYPE_ANNOTATIONS("copyTypeAnnotations"),

/** Option for ArrayList builder usage. */
USING_ARRAY_LIST_BUILDER("usingArrayListBuilder"),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ private BuilderConfiguration parseOptionsFromMirror(AnnotationMirror optionsMirr
builder.generateAddToCollectionHelpers(OptionState.valueOf(enumValue));
case "generateUnboxedOptional" ->
builder.generateUnboxedOptional(OptionState.valueOf(enumValue));
case "copyTypeAnnotations" -> builder.copyTypeAnnotations(OptionState.valueOf(enumValue));
case "usingArrayListBuilder" ->
builder.usingArrayListBuilder(OptionState.valueOf(enumValue));
case "usingArrayListBuilderWithElementBuilders" ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,10 @@ private static Optional<FieldDto> createFieldDto(
// Extract annotations from the field parameter
List<AnnotationDto> annotations = FieldAnnotationExtractor.extractAnnotations(param, context);

// Annotations could be assigned to Type or Parameter.
// To avoid duplication in generated code, we need to remove the duplications here.
annotations.removeAll(fieldType.getAnnotations());

// Check if field has non-null constraint (annotation or primitive type)
if (FieldAnnotationExtractor.hasNonNullConstraint(param)
|| fieldTypeMirror.getKind().isPrimitive()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public BuilderConfiguration readBuilderConfiguration() {
.generateAddToCollectionHelpers(
readOptionState(CompilerArgumentsEnum.GENERATE_ADD_TO_COLLECTION_HELPERS))
.generateUnboxedOptional(readOptionState(CompilerArgumentsEnum.GENERATE_UNBOXED_OPTIONAL))
.copyTypeAnnotations(readOptionState(CompilerArgumentsEnum.COPY_TYPE_ANNOTATIONS))
.usingArrayListBuilder(readOptionState(CompilerArgumentsEnum.USING_ARRAY_LIST_BUILDER))
.usingArrayListBuilderWithElementBuilders(
readOptionState(CompilerArgumentsEnum.USING_ARRAY_LIST_BUILDER_WITH_ELEMENT_BUILDERS))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,30 @@ public static List<AnnotationDto> extractAnnotations(
return annotations;
}

/**
* Extracts annotations from a type mirror. Filters out annotations that should not be copied to
* the builder.
*
* @param typeMirror the type mirror containing annotations
* @param context processing context
* @return list of annotations to be copied to the builder field
*/
public static List<AnnotationDto> extractAnnotations(
javax.lang.model.type.TypeMirror typeMirror, ProcessingContext context) {
List<AnnotationDto> annotations = new ArrayList<>();
List<? extends AnnotationMirror> annotationMirrors = typeMirror.getAnnotationMirrors();

context.debug(
" -> Extracting %d annotation(s) from type %s",
annotationMirrors.size(), typeMirror.toString());

for (AnnotationMirror mirror : annotationMirrors) {
extractAnnotation(mirror, context).ifPresent(annotations::add);
}

return annotations;
}

/**
* Extracts a single annotation from an AnnotationMirror. Filters out annotations that should not
* be copied to the builder.
Expand Down
Loading
Loading