#1479 Add support for Builders with multiple build methods (#1498)

* Add new @Builder annotation for defining a build method
* When there are multiple build methods look for a method named `build` and if found use it
* If @Builder is defined than look for a build method with the defined method
* When a type has multiple builder creation methods throw an exception and don't use the builder

Defaulting to a method named `build` will make sure that a correct method is selected for:
* FreeBuilder - it has two methods: `build` and `buildPartial`
* Protobuf - it has three methods: `getDefaultInstanceForType`, `build` and `buildPartial`
This commit is contained in:
Filip Hrisafov 2018-07-12 23:16:53 +02:00 committed by GitHub
parent 62ffa3fa43
commit ef270caecb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1109 additions and 58 deletions

View File

@ -97,4 +97,23 @@ public @interface BeanMapping {
* @since 1.3 * @since 1.3
*/ */
String[] ignoreUnmappedSourceProperties() default {}; String[] ignoreUnmappedSourceProperties() default {};
/**
* The information that should be used for the builder mappings. This can be used to define custom build methods
* for the builder strategy that one uses.
*
* If no builder is defined the builder given via {@link MapperConfig#builder()} or {@link Mapper#builder()}
* will be applied.
* <p>
* NOTE: In case no builder is defined here, in {@link Mapper} or {@link MapperConfig} and there is a single
* build method, then that method would be used.
* <p>
* If the builder is defined and there is a single method that does not match the name of the finisher than
* a compile error will occurs
*
* @return the builder information for the method level
*
* @since 1.3
*/
Builder builder() default @Builder;
} }

View File

@ -0,0 +1,45 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mapstruct.util.Experimental;
/**
* Configuration of builders, e.g. the name of the final build method.
*
* @author Filip Hrisafov
*
* @since 1.3
*/
@Retention(RetentionPolicy.CLASS)
@Target({})
@Experimental
public @interface Builder {
/**
* The name of the build method that needs to be invoked on the builder to create the type being build
*
* @return the method that needs to tbe invoked on the builder
*/
String buildMethod() default "build";
}

View File

@ -18,8 +18,6 @@
*/ */
package org.mapstruct; package org.mapstruct;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -27,6 +25,8 @@ import java.lang.annotation.Target;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
/** /**
* Marks an interface or abstract class as a mapper and activates the generation of a implementation of that type via * Marks an interface or abstract class as a mapper and activates the generation of a implementation of that type via
* MapStruct. * MapStruct.
@ -200,4 +200,22 @@ public @interface Mapper {
*/ */
boolean disableSubMappingMethodsGeneration() default false; boolean disableSubMappingMethodsGeneration() default false;
/**
* The information that should be used for the builder mappings. This can be used to define custom build methods
* for the builder strategy that one uses.
*
* If no builder is defined the builder given via {@link MapperConfig#builder()} will be applied.
*
* <p>
* NOTE: In case no builder is defined here, in {@link BeanMapping} or {@link MapperConfig} and there is a single
* build method, then that method would be used.
* <p>
* If the builder is defined and there is a single method that does not match the name of the finisher than
* a compile error will occurs
*
* @return the builder information
*
* @since 1.3
*/
Builder builder() default @Builder;
} }

View File

@ -18,8 +18,6 @@
*/ */
package org.mapstruct; package org.mapstruct;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -27,6 +25,8 @@ import java.lang.annotation.Target;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
/** /**
* Marks a class or interface as configuration source for generated mappers. This allows to share common configurations * Marks a class or interface as configuration source for generated mappers. This allows to share common configurations
* between several mapper classes. * between several mapper classes.
@ -186,4 +186,24 @@ public @interface MapperConfig {
* @since 1.2 * @since 1.2
*/ */
boolean disableSubMappingMethodsGeneration() default false; boolean disableSubMappingMethodsGeneration() default false;
/**
* The information that should be used for the builder mappings. This can be used to define custom build methods
* for the builder strategy that one uses.
*
* <p>
* Can be overridden by {@link MapperConfig#builder()}.
*
* <p>
* NOTE: In case no builder is defined here, in {@link BeanMapping} or {@link Mapper} and there is a single
* build method, then that method would be used.
* <p>
* If the builder is defined and there is a single method that does not match the name of the finisher than
* a compile error will occurs
*
* @return the builder information
*
* @since 1.3
*/
Builder builder() default @Builder;
} }

View File

@ -639,6 +639,12 @@ The default implementation of the `BuilderProvider` assumes the following:
So for example `Person` has a public static method that returns `PersonBuilder`. So for example `Person` has a public static method that returns `PersonBuilder`.
* The builder type has a parameterless public method (build method) that returns the type being build * The builder type has a parameterless public method (build method) that returns the type being build
In our example `PersonBuilder` has a method returning `Person`. In our example `PersonBuilder` has a method returning `Person`.
* In case there are multiple build methods, MapStruct will look for a method called `build` if such methods exists
than this would be used, otherwise a compilation error would be created.
* A specific build method can be defined by using `@Builder` within: `@BeanMapping`, `@Mapper` or `@MapperConfig`
* In case there are multiple builder creation methods that satisfy the above conditions then a `MoreThanOneBuilderCreationMethodException`
will be thrown from the `DefaultBuilderProvider` SPI.
In case of a `MoreThanOneBuilderCreationMethodException` MapStruct will write a warning in the compilation and not use any builder.
If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look for setters into that type). If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look for setters into that type).
To finish the mapping MapStruct generates code that will invoke the build method of the builder. To finish the mapping MapStruct generates code that will invoke the build method of the builder.

View File

@ -287,7 +287,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return null; return null;
} }
return MethodReference.forMethodCall( builderType.getBuildMethod() ); return BuilderFinisherMethodResolver.getBuilderFinisherMethod( method, builderType, ctx );
} }
/** /**

View File

@ -0,0 +1,101 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.internal.model;
import java.util.Collection;
import javax.lang.model.element.ExecutableElement;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.source.BeanMapping;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.util.MapperConfiguration;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
* @author Filip Hrisafov
*/
public class BuilderFinisherMethodResolver {
private static final String DEFAULT_BUILD_METHOD_NAME = "build";
private BuilderFinisherMethodResolver() {
}
public static MethodReference getBuilderFinisherMethod(Method method, BuilderType builderType,
MappingBuilderContext ctx) {
Collection<ExecutableElement> buildMethods = builderType.getBuildMethods();
if ( buildMethods.isEmpty() ) {
//If we reach this method this should never happen
return null;
}
BuilderPrism builderMapping = builderMappingPrism( method, ctx );
if ( builderMapping == null && buildMethods.size() == 1 ) {
return MethodReference.forMethodCall( first( buildMethods ).getSimpleName().toString() );
}
else {
String buildMethodPattern = DEFAULT_BUILD_METHOD_NAME;
if ( builderMapping != null ) {
buildMethodPattern = builderMapping.buildMethod();
}
for ( ExecutableElement buildMethod : buildMethods ) {
String methodName = buildMethod.getSimpleName().toString();
if ( methodName.matches( buildMethodPattern ) ) {
return MethodReference.forMethodCall( methodName );
}
}
if ( builderMapping == null ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.BUILDER_NO_BUILD_METHOD_FOUND_DEFAULT,
buildMethodPattern,
builderType.getBuilder(),
builderType.getBuildingType(),
Strings.join( buildMethods, ", " )
);
}
else {
ctx.getMessager().printMessage(
method.getExecutable(),
builderMapping.mirror,
Message.BUILDER_NO_BUILD_METHOD_FOUND,
buildMethodPattern,
builderType.getBuilder(),
builderType.getBuildingType(),
Strings.join( buildMethods, ", " )
);
}
}
return null;
}
private static BuilderPrism builderMappingPrism(Method method, MappingBuilderContext ctx) {
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
if ( beanMapping != null && beanMapping.getBuilder() != null ) {
return beanMapping.getBuilder();
}
return MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() ).getBuilderPrism();
}
}

View File

@ -18,6 +18,7 @@
*/ */
package org.mapstruct.ap.internal.model.common; package org.mapstruct.ap.internal.model.common;
import java.util.Collection;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
@ -33,20 +34,20 @@ public class BuilderType {
private final Type owningType; private final Type owningType;
private final Type buildingType; private final Type buildingType;
private final ExecutableElement builderCreationMethod; private final ExecutableElement builderCreationMethod;
private final ExecutableElement buildMethod; private final Collection<ExecutableElement> buildMethods;
private BuilderType( private BuilderType(
Type builder, Type builder,
Type owningType, Type owningType,
Type buildingType, Type buildingType,
ExecutableElement builderCreationMethod, ExecutableElement builderCreationMethod,
ExecutableElement buildMethod Collection<ExecutableElement> buildMethods
) { ) {
this.builder = builder; this.builder = builder;
this.owningType = owningType; this.owningType = owningType;
this.buildingType = buildingType; this.buildingType = buildingType;
this.builderCreationMethod = builderCreationMethod; this.builderCreationMethod = builderCreationMethod;
this.buildMethod = buildMethod; this.buildMethods = buildMethods;
} }
/** /**
@ -87,18 +88,17 @@ public class BuilderType {
} }
/** /**
* The name of the method that needs to be invoked on the builder to create the type being built. * The build methods that can be invoked to create the type being built.
* * @return the build methods that can be invoked to create the type being built
* @return the name of the method that needs to be invoked on the type that is being built
*/ */
public String getBuildMethod() { public Collection<ExecutableElement> getBuildMethods() {
return buildMethod.getSimpleName().toString(); return buildMethods;
} }
public BuilderInfo asBuilderInfo() { public BuilderInfo asBuilderInfo() {
return new BuilderInfo.Builder() return new BuilderInfo.Builder()
.builderCreationMethod( this.builderCreationMethod ) .builderCreationMethod( this.builderCreationMethod )
.buildMethod( this.buildMethod ) .buildMethod( this.buildMethods )
.build(); .build();
} }
@ -107,11 +107,6 @@ public class BuilderType {
if ( builderInfo == null ) { if ( builderInfo == null ) {
return null; return null;
} }
ExecutableElement buildMethod = builderInfo.getBuildMethod();
if ( !typeUtils.isAssignable( buildMethod.getReturnType(), typeToBuild.getTypeMirror() ) ) {
//TODO throw error
throw new IllegalArgumentException( "Build return type is not assignable" );
}
Type builder = typeFactory.getType( builderInfo.getBuilderCreationMethod().getReturnType() ); Type builder = typeFactory.getType( builderInfo.getBuilderCreationMethod().getReturnType() );
ExecutableElement builderCreationMethod = builderInfo.getBuilderCreationMethod(); ExecutableElement builderCreationMethod = builderInfo.getBuilderCreationMethod();
@ -133,7 +128,7 @@ public class BuilderType {
owner, owner,
typeToBuild, typeToBuild,
builderCreationMethod, builderCreationMethod,
buildMethod builderInfo.getBuildMethods()
); );
} }
} }

View File

@ -55,11 +55,16 @@ import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.Collections;
import org.mapstruct.ap.internal.util.Extractor;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.JavaStreamConstants; import org.mapstruct.ap.internal.util.JavaStreamConstants;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.RoundContext; import org.mapstruct.ap.internal.util.RoundContext;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor;
import org.mapstruct.ap.spi.BuilderInfo; import org.mapstruct.ap.spi.BuilderInfo;
import org.mapstruct.ap.spi.MoreThanOneBuilderCreationMethodException;
import org.mapstruct.ap.spi.TypeHierarchyErroneousException; import org.mapstruct.ap.spi.TypeHierarchyErroneousException;
import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor;
@ -73,8 +78,17 @@ import static org.mapstruct.ap.internal.model.common.ImplementationType.withLoad
*/ */
public class TypeFactory { public class TypeFactory {
private static final Extractor<BuilderInfo, String> BUILDER_INFO_CREATION_METHOD_EXTRACTOR =
new Extractor<BuilderInfo, String>() {
@Override
public String apply(BuilderInfo builderInfo) {
return builderInfo.getBuilderCreationMethod().toString();
}
};
private final Elements elementUtils; private final Elements elementUtils;
private final Types typeUtils; private final Types typeUtils;
private final FormattingMessager messager;
private final RoundContext roundContext; private final RoundContext roundContext;
private final TypeMirror iterableType; private final TypeMirror iterableType;
@ -85,9 +99,10 @@ public class TypeFactory {
private final Map<String, ImplementationType> implementationTypes = new HashMap<String, ImplementationType>(); private final Map<String, ImplementationType> implementationTypes = new HashMap<String, ImplementationType>();
private final Map<String, String> importedQualifiedTypesBySimpleName = new HashMap<String, String>(); private final Map<String, String> importedQualifiedTypesBySimpleName = new HashMap<String, String>();
public TypeFactory(Elements elementUtils, Types typeUtils, RoundContext roundContext) { public TypeFactory(Elements elementUtils, Types typeUtils, FormattingMessager messager, RoundContext roundContext) {
this.elementUtils = elementUtils; this.elementUtils = elementUtils;
this.typeUtils = typeUtils; this.typeUtils = typeUtils;
this.messager = messager;
this.roundContext = roundContext; this.roundContext = roundContext;
iterableType = typeUtils.erasure( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); iterableType = typeUtils.erasure( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() );
@ -502,9 +517,21 @@ public class TypeFactory {
} }
private BuilderInfo findBuilder(TypeMirror type) { private BuilderInfo findBuilder(TypeMirror type) {
return roundContext.getAnnotationProcessorContext() try {
.getBuilderProvider() return roundContext.getAnnotationProcessorContext()
.findBuilderInfo( type, elementUtils, typeUtils ); .getBuilderProvider()
.findBuilderInfo( type, elementUtils, typeUtils );
}
catch ( MoreThanOneBuilderCreationMethodException ex ) {
messager.printMessage(
typeUtils.asElement( type ),
Message.BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD,
type,
Strings.join( ex.getBuilderInfo(), ", ", BUILDER_INFO_CREATION_METHOD_EXTRACTOR )
);
}
return null;
} }
private TypeMirror getComponentType(TypeMirror mirror) { private TypeMirror getComponentType(TypeMirror mirror) {

View File

@ -25,6 +25,7 @@ import javax.lang.model.type.TypeKind;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.prism.BeanMappingPrism; import org.mapstruct.ap.internal.prism.BeanMappingPrism;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; import org.mapstruct.ap.internal.prism.ReportingPolicyPrism;
import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.FormattingMessager;
@ -42,6 +43,7 @@ public class BeanMapping {
private final ReportingPolicyPrism reportingPolicy; private final ReportingPolicyPrism reportingPolicy;
private final boolean ignoreByDefault; private final boolean ignoreByDefault;
private final List<String> ignoreUnmappedSourceProperties; private final List<String> ignoreUnmappedSourceProperties;
private final BuilderPrism builder;
/** /**
* creates a mapping for inheritance. Will set ignoreByDefault to false. * creates a mapping for inheritance. Will set ignoreByDefault to false.
@ -55,7 +57,8 @@ public class BeanMapping {
map.nullValueMappingStrategy, map.nullValueMappingStrategy,
map.reportingPolicy, map.reportingPolicy,
false, false,
map.ignoreUnmappedSourceProperties map.ignoreUnmappedSourceProperties,
map.builder
); );
} }
@ -74,9 +77,15 @@ public class BeanMapping {
: NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() ); : NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() );
boolean ignoreByDefault = beanMapping.ignoreByDefault(); boolean ignoreByDefault = beanMapping.ignoreByDefault();
BuilderPrism builderMapping = null;
if ( beanMapping.values.builder() != null ) {
builderMapping = beanMapping.builder();
}
if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty() if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty()
&& beanMapping.ignoreUnmappedSourceProperties().isEmpty() && beanMapping.ignoreUnmappedSourceProperties().isEmpty()
&& ( nullValueMappingStrategy == null ) && !ignoreByDefault ) { && ( nullValueMappingStrategy == null ) && !ignoreByDefault
&& builderMapping == null ) {
messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS ); messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS );
} }
@ -94,7 +103,8 @@ public class BeanMapping {
nullValueMappingStrategy, nullValueMappingStrategy,
null, null,
ignoreByDefault, ignoreByDefault,
beanMapping.ignoreUnmappedSourceProperties() beanMapping.ignoreUnmappedSourceProperties(),
builderMapping
); );
} }
@ -105,17 +115,18 @@ public class BeanMapping {
* @return bean mapping that needs to be used for Mappings * @return bean mapping that needs to be used for Mappings
*/ */
public static BeanMapping forForgedMethods() { public static BeanMapping forForgedMethods() {
return new BeanMapping( null, null, ReportingPolicyPrism.IGNORE, false, Collections.<String>emptyList() ); return new BeanMapping( null, null, ReportingPolicyPrism.IGNORE, false, Collections.<String>emptyList(), null );
} }
private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms, private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms,
ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault, ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault,
List<String> ignoreUnmappedSourceProperties) { List<String> ignoreUnmappedSourceProperties, BuilderPrism builder) {
this.selectionParameters = selectionParameters; this.selectionParameters = selectionParameters;
this.nullValueMappingStrategy = nvms; this.nullValueMappingStrategy = nvms;
this.reportingPolicy = reportingPolicy; this.reportingPolicy = reportingPolicy;
this.ignoreByDefault = ignoreByDefault; this.ignoreByDefault = ignoreByDefault;
this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties; this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties;
this.builder = builder;
} }
public SelectionParameters getSelectionParameters() { public SelectionParameters getSelectionParameters() {
@ -137,4 +148,8 @@ public class BeanMapping {
public List<String> getIgnoreUnmappedSourceProperties() { public List<String> getIgnoreUnmappedSourceProperties() {
return ignoreUnmappedSourceProperties; return ignoreUnmappedSourceProperties;
} }
public BuilderPrism getBuilder() {
return builder;
}
} }

View File

@ -24,6 +24,7 @@ import javax.xml.bind.annotation.XmlElementRef;
import org.mapstruct.AfterMapping; import org.mapstruct.AfterMapping;
import org.mapstruct.BeanMapping; import org.mapstruct.BeanMapping;
import org.mapstruct.BeforeMapping; import org.mapstruct.BeforeMapping;
import org.mapstruct.Builder;
import org.mapstruct.Context; import org.mapstruct.Context;
import org.mapstruct.DecoratedWith; import org.mapstruct.DecoratedWith;
import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritConfiguration;
@ -71,6 +72,7 @@ import net.java.dev.hickory.prism.GeneratePrisms;
@GeneratePrism(value = ValueMapping.class, publicAccess = true), @GeneratePrism(value = ValueMapping.class, publicAccess = true),
@GeneratePrism(value = ValueMappings.class, publicAccess = true), @GeneratePrism(value = ValueMappings.class, publicAccess = true),
@GeneratePrism(value = Context.class, publicAccess = true), @GeneratePrism(value = Context.class, publicAccess = true),
@GeneratePrism(value = Builder.class, publicAccess = true),
// external types // external types
@GeneratePrism(value = XmlElementDecl.class, publicAccess = true), @GeneratePrism(value = XmlElementDecl.class, publicAccess = true),

View File

@ -64,6 +64,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
this.typeFactory = new TypeFactory( this.typeFactory = new TypeFactory(
processingEnvironment.getElementUtils(), processingEnvironment.getElementUtils(),
delegatingTypes, delegatingTypes,
messager,
roundContext roundContext
); );
this.options = options; this.options = options;

View File

@ -28,6 +28,7 @@ import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.prism.BuilderPrism;
import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.internal.prism.InjectionStrategyPrism; import org.mapstruct.ap.internal.prism.InjectionStrategyPrism;
import org.mapstruct.ap.internal.prism.MapperConfigPrism; import org.mapstruct.ap.internal.prism.MapperConfigPrism;
@ -238,6 +239,18 @@ public class MapperConfiguration {
return mapperPrism.disableSubMappingMethodsGeneration(); // fall back to default defined in the annotation return mapperPrism.disableSubMappingMethodsGeneration(); // fall back to default defined in the annotation
} }
public BuilderPrism getBuilderPrism() {
if ( mapperPrism.values.builder() != null ) {
return mapperPrism.builder();
}
else if ( mapperConfigPrism != null && mapperConfigPrism.values.builder() != null ) {
return mapperConfigPrism.builder();
}
else {
return null;
}
}
public DeclaredType config() { public DeclaredType config() {
return config; return config;
} }

View File

@ -100,6 +100,10 @@ public enum Message {
GENERAL_NOT_ALL_FORGED_CREATED( "Internal Error in creation of Forged Methods, it was expected all Forged Methods to finished with creation, but %s did not" ), GENERAL_NOT_ALL_FORGED_CREATED( "Internal Error in creation of Forged Methods, it was expected all Forged Methods to finished with creation, but %s did not" ),
GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible parameterless constructor." ), GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible parameterless constructor." ),
BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD( "More than one builder creation method for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),
BUILDER_NO_BUILD_METHOD_FOUND("No build method \"%s\" found in \"%s\" for \"%s\". Found methods: \"%s\".", Diagnostic.Kind.ERROR ),
BUILDER_NO_BUILD_METHOD_FOUND_DEFAULT("No build method \"%s\" found in \"%s\" for \"%s\". Found methods: \"%s\". Consider to add @Builder in order to select the correct build method.", Diagnostic.Kind.ERROR ),
RETRIEVAL_NO_INPUT_ARGS( "Can't generate mapping method with no input arguments." ), RETRIEVAL_NO_INPUT_ARGS( "Can't generate mapping method with no input arguments." ),
RETRIEVAL_DUPLICATE_MAPPING_TARGETS( "Can't generate mapping method with more than one @MappingTarget parameter." ), RETRIEVAL_DUPLICATE_MAPPING_TARGETS( "Can't generate mapping method with more than one @MappingTarget parameter." ),
RETRIEVAL_VOID_MAPPING_METHOD( "Can't generate mapping method with return type void." ), RETRIEVAL_VOID_MAPPING_METHOD( "Can't generate mapping method with return type void." ),

View File

@ -18,6 +18,7 @@
*/ */
package org.mapstruct.ap.spi; package org.mapstruct.ap.spi;
import java.util.Collection;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
/** /**
@ -28,11 +29,11 @@ import javax.lang.model.element.ExecutableElement;
public class BuilderInfo { public class BuilderInfo {
private final ExecutableElement builderCreationMethod; private final ExecutableElement builderCreationMethod;
private final ExecutableElement buildMethod; private final Collection<ExecutableElement> buildMethods;
private BuilderInfo(ExecutableElement builderCreationMethod, ExecutableElement buildMethod) { private BuilderInfo(ExecutableElement builderCreationMethod, Collection<ExecutableElement> buildMethods) {
this.builderCreationMethod = builderCreationMethod; this.builderCreationMethod = builderCreationMethod;
this.buildMethod = buildMethod; this.buildMethods = buildMethods;
} }
/** /**
@ -50,18 +51,18 @@ public class BuilderInfo {
} }
/** /**
* The method that can be used to build the type being built. * The methods that can be used to build the type being built.
* This should be a {@code public} method within the builder itself * This should be {@code public} methods within the builder itself
* *
* @return the build method for the type * @return the build method for the type
*/ */
public ExecutableElement getBuildMethod() { public Collection<ExecutableElement> getBuildMethods() {
return buildMethod; return buildMethods;
} }
public static class Builder { public static class Builder {
private ExecutableElement builderCreationMethod; private ExecutableElement builderCreationMethod;
private ExecutableElement buildMethod; private Collection<ExecutableElement> buildMethods;
/** /**
* @see BuilderInfo#getBuilderCreationMethod() * @see BuilderInfo#getBuilderCreationMethod()
@ -72,10 +73,10 @@ public class BuilderInfo {
} }
/** /**
* @see BuilderInfo#getBuildMethod() * @see BuilderInfo#getBuildMethods()
*/ */
public Builder buildMethod(ExecutableElement method) { public Builder buildMethod(Collection<ExecutableElement> methods) {
this.buildMethod = method; this.buildMethods = methods;
return this; return this;
} }
@ -87,10 +88,13 @@ public class BuilderInfo {
if ( builderCreationMethod == null ) { if ( builderCreationMethod == null ) {
throw new IllegalArgumentException( "Builder creation method is mandatory" ); throw new IllegalArgumentException( "Builder creation method is mandatory" );
} }
else if ( buildMethod == null ) { else if ( buildMethods == null ) {
throw new IllegalArgumentException( "Build method is mandatory" ); throw new IllegalArgumentException( "Build methods are mandatory" );
} }
return new BuilderInfo( builderCreationMethod, buildMethod ); else if ( buildMethods.isEmpty() ) {
throw new IllegalArgumentException( "Build methods must not be empty" );
}
return new BuilderInfo( builderCreationMethod, buildMethods );
} }
} }
} }

View File

@ -40,6 +40,8 @@ public interface BuilderProvider {
* *
* @throws TypeHierarchyErroneousException if the type that needs to be visited is not ready yet, this signals the * @throws TypeHierarchyErroneousException if the type that needs to be visited is not ready yet, this signals the
* MapStruct processor to postpone the generation of the mappers to the next round * MapStruct processor to postpone the generation of the mappers to the next round
* @throws MoreThanOneBuilderCreationMethodException if {@code type} has more than one method that can create the
* builder
*/ */
BuilderInfo findBuilderInfo(TypeMirror type, Elements elements, Types types); BuilderInfo findBuilderInfo(TypeMirror type, Elements elements, Types types);
} }

View File

@ -18,6 +18,9 @@
*/ */
package org.mapstruct.ap.spi; package org.mapstruct.ap.spi;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
@ -141,17 +144,22 @@ public class DefaultBuilderProvider implements BuilderProvider {
* <p> * <p>
* The default implementation iterates over all the methods in {@code typeElement} and uses * The default implementation iterates over all the methods in {@code typeElement} and uses
* {@link DefaultBuilderProvider#isPossibleBuilderCreationMethod(ExecutableElement, TypeElement, Types)} and * {@link DefaultBuilderProvider#isPossibleBuilderCreationMethod(ExecutableElement, TypeElement, Types)} and
* {@link DefaultBuilderProvider#findBuildMethod(TypeElement, TypeElement, Types)} to create the * {@link DefaultBuilderProvider#findBuildMethods(TypeElement, TypeElement, Types)} to create the
* {@link BuilderInfo}. * {@link BuilderInfo}.
* <p> * <p>
* The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the
* {@code typeElement} should be ignored. * {@code typeElement} should be ignored.
* <p>
* In case there are multiple {@link BuilderInfo} then a {@link MoreThanOneBuilderCreationMethodException} is
* thrown.
* *
* @param typeElement the type element for which a builder searched * @param typeElement the type element for which a builder searched
* @param elements the util elements that can be used for operating on the type element * @param elements the util elements that can be used for operating on the type element
* @param types the util types that can be used for operation on {@link TypeMirror}(s) * @param types the util types that can be used for operation on {@link TypeMirror}(s)
* *
* @return the {@link BuilderInfo} or {@code null} if no builder was found for the type * @return the {@link BuilderInfo} or {@code null} if no builder was found for the type
* {@link DefaultBuilderProvider#findBuildMethods(TypeElement, TypeElement, Types)}
* @throws MoreThanOneBuilderCreationMethodException if there are multiple builder creation methods
*/ */
protected BuilderInfo findBuilderInfo(TypeElement typeElement, Elements elements, Types types) { protected BuilderInfo findBuilderInfo(TypeElement typeElement, Elements elements, Types types) {
if ( shouldIgnore( typeElement ) ) { if ( shouldIgnore( typeElement ) ) {
@ -159,18 +167,28 @@ public class DefaultBuilderProvider implements BuilderProvider {
} }
List<ExecutableElement> methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() ); List<ExecutableElement> methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() );
List<BuilderInfo> builderInfo = new ArrayList<BuilderInfo>();
for ( ExecutableElement method : methods ) { for ( ExecutableElement method : methods ) {
if ( isPossibleBuilderCreationMethod( method, typeElement, types ) ) { if ( isPossibleBuilderCreationMethod( method, typeElement, types ) ) {
TypeElement builderElement = getTypeElement( method.getReturnType() ); TypeElement builderElement = getTypeElement( method.getReturnType() );
ExecutableElement buildMethod = findBuildMethod( builderElement, typeElement, types ); Collection<ExecutableElement> buildMethods = findBuildMethods( builderElement, typeElement, types );
if ( buildMethod != null ) { if ( !buildMethods.isEmpty() ) {
return new BuilderInfo.Builder() builderInfo.add( new BuilderInfo.Builder()
.builderCreationMethod( method ) .builderCreationMethod( method )
.buildMethod( buildMethod ) .buildMethod( buildMethods )
.build(); .build()
);
} }
} }
} }
if ( builderInfo.size() == 1 ) {
return builderInfo.get( 0 );
}
else if ( builderInfo.size() > 1 ) {
throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), builderInfo );
}
return findBuilderInfo( typeElement.getSuperclass(), elements, types ); return findBuilderInfo( typeElement.getSuperclass(), elements, types );
} }
@ -207,30 +225,40 @@ public class DefaultBuilderProvider implements BuilderProvider {
* <p> * <p>
* The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the
* {@code builderElement} should be ignored, i.e. not checked for build elements. * {@code builderElement} should be ignored, i.e. not checked for build elements.
* * <p>
* If there are multiple methods that satisfy
* {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement, Types)} and one of those methods
* is names {@code build} that that method would be considered as a build method.
* @param builderElement the element for the builder * @param builderElement the element for the builder
* @param typeElement the element for the type that is being built * @param typeElement the element for the type that is being built
* @param types the util types tat can be used for operations on {@link TypeMirror}(s) * @param types the util types tat can be used for operations on {@link TypeMirror}(s)
* *
* @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not * @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not
* {@code build}
*/ */
protected ExecutableElement findBuildMethod(TypeElement builderElement, TypeElement typeElement, Types types) { protected Collection<ExecutableElement> findBuildMethods(TypeElement builderElement, TypeElement typeElement,
Types types) {
if ( shouldIgnore( builderElement ) ) { if ( shouldIgnore( builderElement ) ) {
return null; return Collections.emptyList();
} }
List<ExecutableElement> builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() ); List<ExecutableElement> builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() );
List<ExecutableElement> buildMethods = new ArrayList<ExecutableElement>();
for ( ExecutableElement buildMethod : builderMethods ) { for ( ExecutableElement buildMethod : builderMethods ) {
if ( isBuildMethod( buildMethod, typeElement, types ) ) { if ( isBuildMethod( buildMethod, typeElement, types ) ) {
return buildMethod; buildMethods.add( buildMethod );
} }
} }
return findBuildMethod( if ( buildMethods.isEmpty() ) {
getTypeElement( builderElement.getSuperclass() ), return findBuildMethods(
typeElement, getTypeElement( builderElement.getSuperclass() ),
types typeElement,
); types
);
}
return buildMethods;
} }
/** /**

View File

@ -0,0 +1,49 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.spi;
import java.util.List;
import javax.lang.model.type.TypeMirror;
/**
* Indicates that a type has too many builder creation methods.
* This exception can be used to signal the MapStruct processor that more than one builder creation method was found.
*
* @author Filip Hrisafov
*/
public class MoreThanOneBuilderCreationMethodException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final TypeMirror type;
private final List<BuilderInfo> builderCreationMethods;
public MoreThanOneBuilderCreationMethodException(TypeMirror type, List<BuilderInfo> builderCreationMethods) {
this.type = type;
this.builderCreationMethods = builderCreationMethods;
}
public TypeMirror getType() {
return type;
}
public List<BuilderInfo> getBuilderInfo() {
return builderCreationMethods;
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.mapstruct.BeanMapping;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.builder.multiple.build.Process;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(config = BuilderMapperConfig.class)
public interface BuilderConfigDefinedMapper {
BuilderConfigDefinedMapper INSTANCE = Mappers.getMapper( BuilderConfigDefinedMapper.class );
Process map(Source source);
@BeanMapping(builder = @Builder(buildMethod = "wrongCreate"))
Process wrongMap(Source source);
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.mapstruct.BeanMapping;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.builder.multiple.build.Process;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(builder = @Builder(buildMethod = "create"))
public interface BuilderDefinedMapper {
BuilderDefinedMapper INSTANCE = Mappers.getMapper( BuilderDefinedMapper.class );
Process map(Source source);
@BeanMapping(builder = @Builder(buildMethod = "wrongCreate"))
Process wrongMap(Source source);
}

View File

@ -0,0 +1,29 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.mapstruct.Builder;
import org.mapstruct.MapperConfig;
/**
* @author Filip Hrisafov
*/
@MapperConfig(builder = @Builder(buildMethod = "create"))
public interface BuilderMapperConfig {
}

View File

@ -0,0 +1,33 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface DefaultBuildMethodMapper {
DefaultBuildMethodMapper INSTANCE = Mappers.getMapper( DefaultBuildMethodMapper.class );
Task map(Source source);
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.mapstruct.BeanMapping;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.builder.multiple.build.Process;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface ErroneousMoreThanOneBuildMethodMapper {
Process map(Source source);
@BeanMapping(builder = @Builder(buildMethod = "missingBuild"))
Process map2(Source source);
}

View File

@ -0,0 +1,32 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.builder.multiple.build.Process;
/**
* @author Filip Hrisafov
*/
@Mapper(builder = @Builder(buildMethod = "mapperBuild"))
public interface ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper {
Process map(Source source);
}

View File

@ -0,0 +1,174 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.test.builder.multiple.build.Process;
import org.mapstruct.ap.test.builder.multiple.builder.Case;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@RunWith(AnnotationProcessorTestRunner.class)
@IssueKey("1479")
@WithClasses({
Process.class,
Case.class,
Task.class,
Source.class
})
public class MultipleBuilderMapperTest {
@WithClasses({
ErroneousMoreThanOneBuildMethodMapper.class
})
@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
type = ErroneousMoreThanOneBuildMethodMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 32,
messageRegExp = "No build method \"build\" found in \".*\\.multiple\\.build\\.Process\\.Builder\" " +
"for \".*\\.multiple\\.build\\.Process\"\\. " +
"Found methods: " +
"\".*wrongCreate\\(\\) ?, " +
".*create\\(\\) ?\"\\. " +
"Consider to add @Builder in order to select the correct build method."
),
@Diagnostic(
type = ErroneousMoreThanOneBuildMethodMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 34,
messageRegExp = "No build method \"missingBuild\" found " +
"in \".*\\.multiple\\.build\\.Process\\.Builder\" " +
"for \".*\\.multiple\\.build\\.Process\"\\. " +
"Found methods: " +
"\".*wrongCreate\\(\\) ?, " +
".*create\\(\\) ?\"\\."
)
})
@Test
public void moreThanOneBuildMethod() {
}
@WithClasses({
ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.class
})
@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
type = ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 31,
messageRegExp =
"No build method \"mapperBuild\" found in \".*\\.multiple\\.build\\.Process\\.Builder\" " +
"for \".*\\.multiple\\.build\\.Process\"\\. " +
"Found methods: " +
"\".*wrongCreate\\(\\) ?, " +
".*create\\(\\) ?\"\\."
)
})
@Test
public void moreThanOneBuildMethodDefinedOnMapper() {
}
@WithClasses({
BuilderDefinedMapper.class
})
@Test
public void builderMappingDefined() {
Process map = BuilderDefinedMapper.INSTANCE.map( new Source( "map" ) );
Process wrongMap = BuilderDefinedMapper.INSTANCE.wrongMap( new Source( "wrongMap" ) );
assertThat( map.getBuildMethod() ).isEqualTo( "create" );
assertThat( wrongMap.getBuildMethod() ).isEqualTo( "wrongCreate" );
}
@WithClasses({
BuilderMapperConfig.class,
BuilderConfigDefinedMapper.class
})
@Test
public void builderMappingMapperConfigDefined() {
Process map = BuilderConfigDefinedMapper.INSTANCE.map( new Source( "map" ) );
Process wrongMap = BuilderConfigDefinedMapper.INSTANCE.wrongMap( new Source( "wrongMap" ) );
assertThat( map.getBuildMethod() ).isEqualTo( "create" );
assertThat( wrongMap.getBuildMethod() ).isEqualTo( "wrongCreate" );
}
@WithClasses({
TooManyBuilderCreationMethodsMapper.class
})
@ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED,
// We have 2 diagnostics, as we don't do caching of the types, so a type is processed multiple types
diagnostics = {
@Diagnostic(
type = Case.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 24,
messageRegExp = "More than one builder creation method for \".*\\.multiple\\.builder.Case\"\\. " +
"Found methods: " +
"\".*wrongBuilder\\(\\) ?, " +
".*builder\\(\\) ?\"\\. " +
"Builder will not be used\\. Consider implementing a custom BuilderProvider SPI\\."
),
@Diagnostic(
type = Case.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 24,
messageRegExp = "More than one builder creation method for \".*\\.multiple\\.builder.Case\"\\. " +
"Found methods: " +
"\".*wrongBuilder\\(\\) ?, " +
".*builder\\(\\) ?\"\\. " +
"Builder will not be used\\. Consider implementing a custom BuilderProvider SPI\\."
)
})
@Test
public void tooManyBuilderCreationMethods() {
Case caseTarget = TooManyBuilderCreationMethodsMapper.INSTANCE.map( new Source( "test" ) );
assertThat( caseTarget ).isNotNull();
assertThat( caseTarget.getName() ).isEqualTo( "test" );
assertThat( caseTarget.getBuilderCreationMethod() ).isNull();
assertThat( caseTarget.getBuildMethod() ).isEqualTo( "constructor" );
}
@WithClasses( {
DefaultBuildMethodMapper.class
} )
@Test
public void defaultBuildMethod() {
Task task = DefaultBuildMethodMapper.INSTANCE.map( new Source( "test" ) );
assertThat( task ).isNotNull();
assertThat( task.getName() ).isEqualTo( "test" );
assertThat( task.getBuilderCreationMethod() ).isEqualTo( "builder" );
assertThat( task.getBuildMethod() ).isEqualTo( "build" );
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
/**
* @author Filip Hrisafov
*/
public class Source {
private final String name;
public Source(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,84 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
/**
* @author Filip Hrisafov
*/
public class Task {
private final String builderCreationMethod;
private final String buildMethod;
private String name;
public Task() {
this.builderCreationMethod = null;
this.buildMethod = "constructor";
}
public Task(Builder builder, String buildMethod) {
this.builderCreationMethod = builder.builderCreationMethod;
this.buildMethod = buildMethod;
this.name = builder.name;
}
public String getBuilderCreationMethod() {
return builderCreationMethod;
}
public String getBuildMethod() {
return buildMethod;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static Builder builder() {
return new Builder( "builder" );
}
public static class Builder {
private String name;
private String builderCreationMethod;
protected Builder(String builderCreationMethod) {
this.builderCreationMethod = builderCreationMethod;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Task wrongCreate() {
return new Task( this, "wrongCreate" );
}
public Task build() {
return new Task( this, "build" );
}
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.builder.multiple.builder.Case;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface TooManyBuilderCreationMethodsMapper {
TooManyBuilderCreationMethodsMapper INSTANCE = Mappers.getMapper( TooManyBuilderCreationMethodsMapper.class );
Case map(Source source);
}

View File

@ -0,0 +1,84 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple.build;
/**
* @author Filip Hrisafov
*/
public class Process {
private final String builderCreationMethod;
private final String buildMethod;
private String name;
public Process() {
this.builderCreationMethod = null;
this.buildMethod = "constructor";
}
public Process(Builder builder, String buildMethod) {
this.builderCreationMethod = builder.builderCreationMethod;
this.buildMethod = buildMethod;
this.name = builder.name;
}
public String getBuilderCreationMethod() {
return builderCreationMethod;
}
public String getBuildMethod() {
return buildMethod;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static Builder builder() {
return new Builder( "builder" );
}
public static class Builder {
private String name;
private String builderCreationMethod;
protected Builder(String builderCreationMethod) {
this.builderCreationMethod = builderCreationMethod;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Process wrongCreate() {
return new Process( this, "wrongCreate" );
}
public Process create() {
return new Process( this, "create" );
}
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.builder.multiple.builder;
/**
* @author Filip Hrisafov
*/
public class Case {
private final String builderCreationMethod;
private final String buildMethod;
private String name;
public Case() {
this.builderCreationMethod = null;
this.buildMethod = "constructor";
}
public Case(Builder builder, String buildMethod) {
this.builderCreationMethod = builder.builderCreationMethod;
this.buildMethod = buildMethod;
this.name = builder.name;
}
public String getBuilderCreationMethod() {
return builderCreationMethod;
}
public String getBuildMethod() {
return buildMethod;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static Builder wrongBuilder() {
return new Builder( "wrongBuilder" );
}
public static Builder builder() {
return new Builder( "builder" );
}
public static class Builder {
private String name;
private String builderCreationMethod;
protected Builder(String builderCreationMethod) {
this.builderCreationMethod = builderCreationMethod;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Case create() {
return new Case( this, "create" );
}
}
}