diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java index fe3836e30..45802ff38 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java @@ -51,6 +51,7 @@ public class FullFeatureCompilationTest { // SPI not working correctly here.. (not picked up) additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" ); switch ( processorType ) { case ORACLE_JAVA_9: diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index f5ddf3867..666dfb1aa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -140,21 +140,22 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { Type returnTypeImpl = getReturnTypeToConstructFromSelectionParameters( selectionParameters ); if ( returnTypeImpl != null ) { factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters ); - if ( factoryMethod != null || canBeConstructed( returnTypeImpl ) ) { + if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; } - else { - reportResultTypeFromBeanMappingNotConstructableError( returnTypeImpl ); - } } - else { - returnTypeImpl = isBuilderRequired() ? returnTypeBuilder.getBuilder() : method.getReturnType(); + else if ( isBuilderRequired() ) { + returnTypeImpl = returnTypeBuilder.getBuilder(); factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters ); - if ( factoryMethod != null || canBeConstructed( returnTypeImpl ) ) { + if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; } - else { - reportReturnTypeNotConstructableError( returnTypeImpl ); + } + else if ( !method.isUpdateMethod() ) { + returnTypeImpl = method.getReturnType(); + factoryMethod = getFactoryMethod( returnTypeImpl, selectionParameters ); + if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) { + returnTypeToConstruct = returnTypeImpl; } } } @@ -383,57 +384,60 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { return null; } - private boolean canBeConstructed(Type typeToBeConstructed) { - return !typeToBeConstructed.isAbstract() - && typeToBeConstructed.isAssignableTo( this.method.getResultType() ) - && typeToBeConstructed.hasEmptyAccessibleConstructor(); - } - - private void reportResultTypeFromBeanMappingNotConstructableError(Type resultType) { + private boolean canResultTypeFromBeanMappingBeConstructed(Type resultType) { + boolean error = true; if ( resultType.isAbstract() ) { ctx.getMessager().printMessage( - method.getExecutable(), - BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, - BEANMAPPING_ABSTRACT, - resultType, - method.getResultType() + method.getExecutable(), + BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, + BEANMAPPING_ABSTRACT, + resultType, + method.getResultType() ); + error = false; } else if ( !resultType.isAssignableTo( method.getResultType() ) ) { ctx.getMessager().printMessage( - method.getExecutable(), - BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, - BEANMAPPING_NOT_ASSIGNABLE, - resultType, - method.getResultType() + method.getExecutable(), + BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, + BEANMAPPING_NOT_ASSIGNABLE, + resultType, + method.getResultType() ); + error = false; } else if ( !resultType.hasEmptyAccessibleConstructor() ) { ctx.getMessager().printMessage( - method.getExecutable(), - BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, - Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, - resultType + method.getExecutable(), + BeanMappingPrism.getInstanceOn( method.getExecutable() ).mirror, + Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, + resultType ); + error = false; } + return error; } - private void reportReturnTypeNotConstructableError(Type returnType) { + private boolean canReturnTypeBeConstructed(Type returnType) { + boolean error = true; if ( returnType.isAbstract() ) { ctx.getMessager().printMessage( - method.getExecutable(), - GENERAL_ABSTRACT_RETURN_TYPE, - returnType + method.getExecutable(), + GENERAL_ABSTRACT_RETURN_TYPE, + returnType ); + error = false; } else if ( !returnType.hasEmptyAccessibleConstructor() ) { ctx.getMessager().printMessage( - method.getExecutable(), - Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, - returnType + method.getExecutable(), + Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, + returnType ); + error = false; } + return error; } /** @@ -1021,4 +1025,3 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } } - diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java index bae5c161b..1b34f4698 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.model.common; import java.util.Collection; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; @@ -102,6 +103,11 @@ public class BuilderType { owner = typeFactory.getType( builderCreationOwner ); } + // When the builderCreationMethod is constructor, its return type is Void. In this case the + // builder type should be the owner type. + if (builderInfo.getBuilderCreationMethod().getKind() == ElementKind.CONSTRUCTOR) { + builder = owner; + } return new BuilderType( builder, diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801BuilderProvider.java new file mode 100644 index 000000000..80766d853 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801BuilderProvider.java @@ -0,0 +1,96 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1801; + +import java.util.List; +import java.util.Objects; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +/** + * @author Zhizhi Deng + */ +public class Issue1801BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + BuilderInfo info = findBuilderInfoFromInnerBuilderClass( typeElement ); + if ( info != null ) { + return info; + } + } + return super.findBuilderInfo( typeElement ); + } + + /** + * Looks for inner builder class in the Immutable interface / abstract class. + * + * The inner builder class should be be declared with the following line + * + *
+ * public static Builder() extends ImmutableItem.Builder { } + *+ * + * The Immutable instance should be created with the following line + * + *
+ * new Item.Builder().withId("123").build(); + *+ * + * @see org.mapstruct.ap.test.bugs._1801.domain.Item + * + * @param typeElement + * @return + */ + private BuilderInfo findBuilderInfoFromInnerBuilderClass(TypeElement typeElement) { + if (shouldIgnore( typeElement )) { + return null; + } + + List
+ * Superclass should expose a static subclass of the Builder to create immutable instance + * {@code public static Builder extends ImmutableItem.Builder}. + * + * @author Zhizhi Deng + */ +@SuppressWarnings({"all"}) +public final class ImmutableItem extends Item { + private final String id; + + private ImmutableItem(String id) { + this.id = id; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * @param value A new value for id + * @return A modified copy of the {@code this} object + */ + public final ImmutableItem withId(String value) { + if (this.id == value) return this; + return new ImmutableItem(value); + } + + /** + * This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values. + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if (this == another) return true; + return another instanceof ImmutableItem + && equalTo((ImmutableItem) another); + } + + private boolean equalTo(ImmutableItem another) { + return id.equals(another.id); + } + + /** + * Computes a hash code from attributes: {@code id}. + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + id.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + "}"; + } + + /** + * Creates an immutable copy of a {@link Item} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItem copyOf(Item instance) { + if (instance instanceof ImmutableItem) { + return (ImmutableItem) instance; + } + return new Builder() + .from(instance) + .build(); + } + + /** + * Builds instances of type {@link ImmutableItem ImmutableItem}. + *
{@code Builder} is not thread-safe and generally should not be stored in a field or collection,
+ * but instead used immediately to create instances.
+ */
+ public static class Builder {
+ private static final long INIT_BIT_ID = 0x1L;
+ private long initBits = 0x1L;
+
+ private String id;
+
+ Builder() {
+ }
+
+ /**
+ * Fill a builder with attribute values from the provided {@code Item} instance.
+ * Regular attribute values will be replaced with those from the given instance.
+ * Absent optional values will not replace present values.
+ * @param instance The instance from which to copy values
+ * @return {@code this} builder for use in a chained invocation
+ */
+ public final Builder from(Item instance) {
+ id(instance.getId());
+ return this;
+ }
+
+ /**
+ * Initializes the value for the {@link Item#getId() id} attribute.
+ * @param id The value for id
+ * @return {@code this} builder for use in a chained invocation
+ */
+ public final Builder id(String id) {
+ this.id = id;
+ initBits &= ~INIT_BIT_ID;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link ImmutableItem ImmutableItem}.
+ * @return An immutable instance of Item
+ * @throws java.lang.IllegalStateException if any required attributes are missing
+ */
+ public ImmutableItem build() {
+ if (initBits != 0) {
+ throw new IllegalStateException(formatRequiredAttributesMessage());
+ }
+ return new ImmutableItem(id);
+ }
+
+ private String formatRequiredAttributesMessage() {
+ List
+ * Use the builder to create immutable instances:
+ * {@code ImmutableItemDTO.builder()}.
+ *
+ * @author Zhizhi Deng
+ */
+public final class ImmutableItemDTO extends ItemDTO {
+ private final String id;
+
+ private ImmutableItemDTO(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return The value of the {@code id} attribute
+ */
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Copy the current immutable object by setting a value for the {@link ItemDTO#getId() id} attribute.
+ * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}.
+ *
+ * @param value A new value for id
+ *
+ * @return A modified copy of the {@code this} object
+ */
+ public ImmutableItemDTO withId(String value) {
+ if ( Objects.equals( this.id, value ) ) {
+ return this;
+ }
+ return new ImmutableItemDTO( value );
+ }
+
+ /**
+ * This instance is equal to all instances of {@code ImmutableItemDTO} that have equal attribute values.
+ *
+ * @return {@code true} if {@code this} is equal to {@code another} instance
+ */
+ @Override
+ public boolean equals(Object another) {
+ if ( this == another ) {
+ return true;
+ }
+ return another instanceof ImmutableItemDTO
+ && equalTo( (ImmutableItemDTO) another );
+ }
+
+ private boolean equalTo(ImmutableItemDTO another) {
+ return id.equals( another.id );
+ }
+
+ /**
+ * Computes a hash code from attributes: {@code id}.
+ *
+ * @return hashCode value
+ */
+ @Override
+ public int hashCode() {
+ int h = 5381;
+ h += ( h << 5 ) + id.hashCode();
+ return h;
+ }
+
+ /**
+ * Prints the immutable value {@code ItemDTO} with attribute values.
+ *
+ * @return A string representation of the value
+ */
+ @Override
+ public String toString() {
+ return "ItemDTO{"
+ + "id=" + id
+ + "}";
+ }
+
+ /**
+ * Creates an immutable copy of a {@link ItemDTO} value.
+ * Uses accessors to get values to initialize the new immutable instance.
+ * If an instance is already immutable, it is returned as is.
+ *
+ * @param instance The instance to copy
+ *
+ * @return A copied immutable ItemDTO instance
+ */
+ public static ImmutableItemDTO copyOf(ItemDTO instance) {
+ if ( instance instanceof ImmutableItemDTO ) {
+ return (ImmutableItemDTO) instance;
+ }
+ return ImmutableItemDTO.builder()
+ .from( instance )
+ .build();
+ }
+
+ /**
+ * Creates a builder for {@link ImmutableItemDTO ImmutableItemDTO}.
+ *
+ * @return A new ImmutableItemDTO builder
+ */
+ public static ImmutableItemDTO.Builder builder() {
+ return new ImmutableItemDTO.Builder();
+ }
+
+ /**
+ * Builds instances of type {@link ImmutableItemDTO ImmutableItemDTO}.
+ * Initialize attributes and then invoke the {@link #build()} method to create an
+ * immutable instance.
+ * {@code Builder} is not thread-safe and generally should not be stored in a field or collection,
+ * but instead used immediately to create instances.
+ */
+ public static final class Builder {
+ private static final long INIT_BIT_ID = 0x1L;
+ private long initBits = 0x1L;
+
+ private String id;
+
+ private Builder() {
+ }
+
+ /**
+ * Fill a builder with attribute values from the provided {@code ItemDTO} instance.
+ * Regular attribute values will be replaced with those from the given instance.
+ * Absent optional values will not replace present values.
+ *
+ * @param instance The instance from which to copy values
+ *
+ * @return {@code this} builder for use in a chained invocation
+ */
+ public Builder from(ItemDTO instance) {
+ id( instance.getId() );
+ return this;
+ }
+
+ /**
+ * Initializes the value for the {@link ItemDTO#getId() id} attribute.
+ *
+ * @param id The value for id
+ *
+ * @return {@code this} builder for use in a chained invocation
+ */
+ public Builder id(String id) {
+ this.id = id;
+ initBits &= ~INIT_BIT_ID;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link ImmutableItemDTO ImmutableItemDTO}.
+ *
+ * @return An immutable instance of ItemDTO
+ *
+ * @throws java.lang.IllegalStateException if any required attributes are missing
+ */
+ public ImmutableItemDTO build() {
+ if ( initBits != 0 ) {
+ throw new IllegalStateException( formatRequiredAttributesMessage() );
+ }
+ return new ImmutableItemDTO( id );
+ }
+
+ private String formatRequiredAttributesMessage() {
+ List