From 952ee8526dc584a754d269a33dc537118da8ac1d Mon Sep 17 00:00:00 2001 From: Andreas Gudian Date: Tue, 24 Feb 2015 11:26:28 +0100 Subject: [PATCH] #168 Allow to inherit mapping method configurations from the @MapperConfig-annotated type, either automatically when all method types match, or explicitly using @InheritConfiguration. --- .../src/main/java/org/mapstruct/Mapper.java | 13 + .../main/java/org/mapstruct/MapperConfig.java | 29 +- .../mapstruct/MappingInheritanceStrategy.java | 47 +++ .../mapstruct/ap/model/BeanMappingMethod.java | 5 +- .../mapstruct/ap/model/EnumMappingMethod.java | 19 +- .../ap/model/IterableMappingMethod.java | 7 +- .../mapstruct/ap/model/MapMappingMethod.java | 7 +- .../mapstruct/ap/model/source/Mapping.java | 35 ++- .../ap/model/source/MappingOptions.java | 137 +++++++++ .../ap/model/source/SourceMethod.java | 257 +++++++---------- .../ap/model/source/SourceReference.java | 27 +- .../source/selector/InheritanceSelector.java | 4 +- .../MappingInheritanceStrategyPrism.java | 31 ++ .../ap/processor/MapperCreationProcessor.java | 268 +++++++++--------- .../processor/MethodRetrievalProcessor.java | 102 +++++-- .../org/mapstruct/ap/util/Collections.java | 13 + .../org/mapstruct/ap/util/MapperConfig.java | 35 ++- .../java/org/mapstruct/ap/util/Message.java | 8 +- .../AutoInheritedConfig.java | 40 +++ .../AutoInheritedDriverConfig.java | 41 +++ .../inheritfromconfig/BaseVehicleDto.java | 35 +++ .../inheritfromconfig/BaseVehicleEntity.java | 44 +++ .../ap/test/inheritfromconfig/CarDto.java | 35 +++ .../ap/test/inheritfromconfig/CarEntity.java | 35 +++ .../CarMapperWithAutoInheritance.java} | 40 +-- .../CarMapperWithExplicitInheritance.java | 49 ++++ .../CarWithDriverEntity.java | 35 +++ ...arWithDriverMapperWithAutoInheritance.java | 37 +++ .../ap/test/inheritfromconfig/DriverDto.java | 35 +++ .../inheritfromconfig/Erroneous1Config.java | 45 +++ .../inheritfromconfig/Erroneous1Mapper.java | 44 +++ .../Erroneous2Mapper.java} | 38 +-- .../InheritFromConfigTest.java | 216 ++++++++++++++ .../ap/test/prism/EnumPrismsTest.java | 12 +- .../InheritInverseConfigurationTest.java | 44 +-- ...urceTargetMapperErroneouslyAnnotated1.java | 57 ---- .../template/InheritConfigurationTest.java | 30 -- ...urceTargetMapperErroneouslyAnnotated1.java | 51 ---- .../SourceTargetMapperSeveralArgs.java | 8 +- .../template/SourceTargetMapperSingle.java | 2 +- 40 files changed, 1432 insertions(+), 585 deletions(-) create mode 100644 core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java create mode 100644 processor/src/main/java/org/mapstruct/ap/model/source/MappingOptions.java create mode 100644 processor/src/main/java/org/mapstruct/ap/prism/MappingInheritanceStrategyPrism.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedDriverConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarEntity.java rename processor/src/test/java/org/mapstruct/ap/test/{template/SourceTargetMapperErroneouslyAnnotated2.java => inheritfromconfig/CarMapperWithAutoInheritance.java} (52%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarMapperWithExplicitInheritance.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverMapperWithAutoInheritance.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/DriverDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Config.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Mapper.java rename processor/src/test/java/org/mapstruct/ap/test/{reverse/SourceTargetMapperErroneouslyAnnotated2.java => inheritfromconfig/Erroneous2Mapper.java} (55%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/reverse/SourceTargetMapperErroneouslyAnnotated1.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperErroneouslyAnnotated1.java diff --git a/core-common/src/main/java/org/mapstruct/Mapper.java b/core-common/src/main/java/org/mapstruct/Mapper.java index ba1f32611..f9540b6a5 100644 --- a/core-common/src/main/java/org/mapstruct/Mapper.java +++ b/core-common/src/main/java/org/mapstruct/Mapper.java @@ -112,4 +112,17 @@ public @interface Mapper { * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapper. */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.DEFAULT; + + /** + * The strategy to use for applying method-level configuration annotations of prototype methods in the interface + * specified with {@link #config()}. Annotations that can be inherited are for example {@link Mapping}, + * {@link IterableMapping}, {@link MapMapping}, or {@link BeanMapping}. + *

+ * If no strategy is configured, the strategy given via {@link MapperConfig#mappingInheritanceStrategy()} will be + * applied, using {@link MappingInheritanceStrategy#EXPLICIT} as default. + * + * @return The strategy to use for applying {@code @Mapping} configurations of prototype methods in the interface + * specified with {@link #config()}. + */ + MappingInheritanceStrategy mappingInheritanceStrategy() default MappingInheritanceStrategy.DEFAULT; } diff --git a/core-common/src/main/java/org/mapstruct/MapperConfig.java b/core-common/src/main/java/org/mapstruct/MapperConfig.java index 6d044193f..14ced439b 100644 --- a/core-common/src/main/java/org/mapstruct/MapperConfig.java +++ b/core-common/src/main/java/org/mapstruct/MapperConfig.java @@ -26,10 +26,18 @@ import java.lang.annotation.Target; import org.mapstruct.factory.Mappers; /** - * Marks a class-, interface-, enum declaration as (common) configuration. - * + * Marks a class- or interface-declaration as (common) configuration. + *

* The {@link #unmappedTargetPolicy() } and {@link #componentModel() } can be overruled by a specific {@link Mapper} * annotation. {@link #uses() } will be used in addition to what is specified in the {@link Mapper} annotation. + *

+ *

+ * Mapping methods defined in the annotated type can be used as prototypes from which method-level annotations + * such as {@code @Mapping}, {@code @IterableMapping}, etc. can be inherited. Depending on the configured + * {@link #mappingInheritanceStrategy()}, the configuration can be inherited either explicitly using + * {@link InheritConfiguration} or {@link InheritInverseConfiguration}, or automatically in case all source and target + * types are assignable. + *

* * @author Sjaak Derksen */ @@ -80,7 +88,7 @@ public @interface MapperConfig { * * @return The strategy applied when propagating the value of collection-typed properties. */ - CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.DEFAULT; + CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY; /** * The strategy to be applied when {@code null} is passed as source value to mapping methods. If no strategy is @@ -88,5 +96,18 @@ public @interface MapperConfig { * * @return The strategy to be applied when {@code null} is passed as source value to mapping methods. */ - NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.DEFAULT; + NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + + /** + * The strategy to use for applying method-level configuration annotations of prototype methods in the interface + * annotated with this annotation. Annotations that can be inherited are for example {@link Mapping}, + * {@link IterableMapping}, {@link MapMapping}, or {@link BeanMapping}. + *

+ * If no strategy is configured, {@link MappingInheritanceStrategy#EXPLICIT} will be used as default. + * + * @return The strategy to use for applying {@code @Mapping} configurations of prototype methods in the interface + * annotated with this annotation. + */ + MappingInheritanceStrategy mappingInheritanceStrategy() + default MappingInheritanceStrategy.EXPLICIT; } diff --git a/core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java b/core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java new file mode 100644 index 000000000..7f9f44d04 --- /dev/null +++ b/core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java @@ -0,0 +1,47 @@ +/** + * Copyright 2012-2015 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; + +/** + * The strategy to use for applying method-level configuration annotations of prototype methods in the interface + * specified with {@link Mapper#config()}. + * + * @author Andreas Gudian + */ +public enum MappingInheritanceStrategy { + /** + * Apply the method-level configuration annotations only if the prototype method is explicitly referenced using + * {@link InheritConfiguration}. + */ + EXPLICIT, + + /** + * Apply the method-level configuration annotations if source and target types of the prototype method are + * assignable from the types of a given mapping method. + */ + AUTO_INHERIT_FROM_CONFIG, + + /** + * When given via {@link Mapper#mappingInheritanceStrategy()}, the value specified via + * {@link MapperConfig#mappingInheritanceStrategy()} will be applied, if present. + *

+ * When given via {@link MapperConfig#mappingInheritanceStrategy()}, the strategy {@link #EXPLICIT} will be applied. + */ + DEFAULT; +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java index 9d8dfa1d5..43b7501fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; + import javax.lang.model.element.ExecutableElement; import javax.tools.Diagnostic; @@ -43,9 +44,9 @@ import org.mapstruct.ap.option.ReportingPolicy; import org.mapstruct.ap.prism.BeanMappingPrism; import org.mapstruct.ap.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.prism.NullValueMappingPrism; -import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Executables; import org.mapstruct.ap.util.MapperConfig; +import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Strings; /** @@ -145,7 +146,7 @@ public class BeanMappingMethod extends MappingMethod { Set handledTargets = new HashSet(); - for ( Map.Entry> entry : method.getMappings().entrySet() ) { + for ( Map.Entry> entry : method.getMappingOptions().getMappings().entrySet() ) { for ( Mapping mapping : entry.getValue() ) { PropertyMapping propertyMapping = null; diff --git a/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java index 853fb186e..898edc36d 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/EnumMappingMethod.java @@ -29,6 +29,8 @@ import org.mapstruct.ap.model.source.SourceMethod; import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Strings; +import static org.mapstruct.ap.util.Collections.first; + /** * A {@link MappingMethod} which maps one enum type to another, optionally configured by one or more * {@link EnumMapping}s. @@ -63,8 +65,7 @@ public class EnumMappingMethod extends MappingMethod { List enumMappings = new ArrayList(); - List sourceEnumConstants - = method.getSourceParameters().iterator().next().getType().getEnumConstants(); + List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); for ( String enumConstant : sourceEnumConstants ) { List mappedConstants = method.getMappingBySourcePropertyName( enumConstant ); @@ -75,7 +76,7 @@ public class EnumMappingMethod extends MappingMethod { else if ( mappedConstants.size() == 1 ) { enumMappings.add( new EnumMapping( - enumConstant, mappedConstants.iterator().next().getTargetName() + enumConstant, first( mappedConstants ).getTargetName() ) ); } @@ -96,13 +97,12 @@ public class EnumMappingMethod extends MappingMethod { } private boolean reportErrorIfMappedEnumConstantsDontExist(SourceMethod method) { - List sourceEnumConstants = - method.getSourceParameters().iterator().next().getType().getEnumConstants(); + List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); List targetEnumConstants = method.getReturnType().getEnumConstants(); boolean foundIncorrectMapping = false; - for ( List mappedConstants : method.getMappings().values() ) { + for ( List mappedConstants : method.getMappingOptions().getMappings().values() ) { for ( Mapping mappedConstant : mappedConstants ) { if ( mappedConstant.getSourceName() == null ) { @@ -118,7 +118,7 @@ public class EnumMappingMethod extends MappingMethod { mappedConstant.getSourceAnnotationValue(), Message.ENUMMAPPING_NON_EXISTING_CONSTANT, mappedConstant.getSourceName(), - method.getSourceParameters().iterator().next().getType() + first( method.getSourceParameters() ).getType() ); foundIncorrectMapping = true; } @@ -148,8 +148,7 @@ public class EnumMappingMethod extends MappingMethod { private boolean reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( SourceMethod method) { - List sourceEnumConstants = - method.getSourceParameters().iterator().next().getType().getEnumConstants(); + List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); List targetEnumConstants = method.getReturnType().getEnumConstants(); List unmappedSourceEnumConstants = new ArrayList(); @@ -182,6 +181,6 @@ public class EnumMappingMethod extends MappingMethod { } public Parameter getSourceParameter() { - return getParameters().iterator().next(); + return first( getParameters() ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java index 37e40268a..c385ca66a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java @@ -20,6 +20,7 @@ package org.mapstruct.ap.model; import java.util.List; import java.util.Set; + import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -30,10 +31,12 @@ import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.prism.NullValueMappingPrism; -import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.MapperConfig; +import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Strings; +import static org.mapstruct.ap.util.Collections.first; + /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one iterable type to another. The collection * elements are mapped either by a {@link TypeConversion} or another mapping method. @@ -82,7 +85,7 @@ public class IterableMappingMethod extends MappingMethod { } public IterableMappingMethod build() { - Type sourceParameterType = method.getSourceParameters().iterator().next().getType(); + Type sourceParameterType = first( method.getSourceParameters() ).getType(); Type resultType = method.getResultType(); Type sourceElementType = sourceParameterType.isArrayType() ? sourceParameterType.getComponentType() : diff --git a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java index 1a676ec0e..0cac68cde 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java @@ -20,6 +20,7 @@ package org.mapstruct.ap.model; import java.util.List; import java.util.Set; + import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.model.assignment.Assignment; @@ -28,10 +29,12 @@ import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.prism.NullValueMappingPrism; -import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.MapperConfig; +import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Strings; +import static org.mapstruct.ap.util.Collections.first; + /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and * values are mapped either by a {@link TypeConversion} or another mapping method if required. @@ -99,7 +102,7 @@ public class MapMappingMethod extends MappingMethod { public MapMappingMethod build() { - List sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters(); + List sourceTypeParams = first( method.getSourceParameters() ).getType().getTypeParameters(); List resultTypeParams = method.getResultType().getTypeParameters(); // find mapping method or conversion for key diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java index 89d857885..8c65463df 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.mapstruct.ap.util.FormattingMessager; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ElementKind; @@ -37,6 +37,7 @@ import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.prism.MappingPrism; import org.mapstruct.ap.prism.MappingsPrism; +import org.mapstruct.ap.util.FormattingMessager; import org.mapstruct.ap.util.Message; /** @@ -61,7 +62,6 @@ public class Mapping { private final AnnotationValue sourceAnnotationValue; private final AnnotationValue targetAnnotationValue; private SourceReference sourceReference; - private SourceReference targetReference; public static Map> fromMappingsPrism(MappingsPrism mappingsAnnotation, ExecutableElement method, @@ -239,10 +239,6 @@ public class Mapping { return sourceReference; } - public SourceReference getTargetReference() { - return targetReference; - } - public TypeMirror getResultType() { return resultType; } @@ -298,6 +294,33 @@ public class Mapping { return reverse; } + /** + * Creates a copy of this mapping, which is adapted to the given method + * + * @param the method to adapt the copy to + */ + public Mapping copyForInheritanceTo(SourceMethod method) { + Mapping mapping = new Mapping( + sourceName, + constant, + javaExpression, + targetName, + dateFormat, + qualifiers, + isIgnored, + mirror, + sourceAnnotationValue, + targetAnnotationValue, + resultType + ); + + if ( sourceReference != null ) { + mapping.sourceReference = sourceReference.copyForInheritanceTo( method ); + } + + return mapping; + } + @Override public String toString() { return "Mapping {" + diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/model/source/MappingOptions.java new file mode 100644 index 000000000..f0db38a9e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/source/MappingOptions.java @@ -0,0 +1,137 @@ +/** + * Copyright 2012-2015 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.model.source; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.model.common.TypeFactory; +import org.mapstruct.ap.util.FormattingMessager; + +/** + * Encapsulates all options specifiable on a mapping method + * + * @author Andreas Gudian + */ +public class MappingOptions { + private Map> mappings; + private IterableMapping iterableMapping; + private MapMapping mapMapping; + private boolean fullyInitialized; + + public MappingOptions(Map> mappings, IterableMapping iterableMapping, MapMapping mapMapping) { + this.mappings = mappings; + this.iterableMapping = iterableMapping; + this.mapMapping = mapMapping; + } + + /** + * @return the {@link Mapping}s configured for this method, keyed by target property name. Only for enum mapping + * methods a target will be mapped by several sources. + */ + public Map> getMappings() { + return mappings; + } + + public IterableMapping getIterableMapping() { + return iterableMapping; + } + + public MapMapping getMapMapping() { + return mapMapping; + } + + public void setMappings(Map> mappings) { + this.mappings = mappings; + } + + public void setIterableMapping(IterableMapping iterableMapping) { + this.iterableMapping = iterableMapping; + } + + public void setMapMapping(MapMapping mapMapping) { + this.mapMapping = mapMapping; + } + + /** + * @return the {@code true}, iff the options have been fully initialized by applying all available inheritance + * options + */ + public boolean isFullyInitialized() { + return fullyInitialized; + } + + public void markAsFullyInitialized() { + this.fullyInitialized = true; + } + + /** + * Merges in all the mapping options configured, giving the already defined options precedence. + * + * @param inherited the options to inherit, may be {@code null} + * @param isInverse if {@code true}, the specified options are from an inverse method + * @param method the source method + * @param messager the messager + * @param typeFactory the type factory + */ + public void applyInheritedOptions(MappingOptions inherited, boolean isInverse, SourceMethod method, + FormattingMessager messager, TypeFactory typeFactory) { + if ( null != inherited ) { + if ( getIterableMapping() == null ) { + if ( inherited.getIterableMapping() != null ) { + setIterableMapping( inherited.getIterableMapping() ); + } + } + + if ( getMapMapping() == null ) { + if ( inherited.getMapMapping() != null ) { + setMapMapping( inherited.getMapMapping() ); + } + } + + Map> newMappings = new HashMap>(); + + for ( List lmappings : inherited.getMappings().values() ) { + for ( Mapping mapping : lmappings ) { + if ( isInverse ) { + mapping = mapping.reverse( method, messager, typeFactory ); + } + + if ( mapping != null ) { + List mappingsOfProperty = newMappings.get( mapping.getTargetName() ); + if ( mappingsOfProperty == null ) { + mappingsOfProperty = new ArrayList(); + newMappings.put( mapping.getTargetName(), mappingsOfProperty ); + } + + mappingsOfProperty.add( mapping.copyForInheritanceTo( method ) ); + } + } + } + + // now add all of its own mappings + newMappings.putAll( getMappings() ); + setMappings( newMappings ); + } + + markAsFullyInitialized(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java index 09a9307bb..29cf89728 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java @@ -19,11 +19,12 @@ package org.mapstruct.ap.model.source; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; -import org.mapstruct.ap.util.FormattingMessager; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.util.Types; @@ -33,9 +34,12 @@ import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.model.source.SourceReference.PropertyEntry; +import org.mapstruct.ap.util.FormattingMessager; import org.mapstruct.ap.util.MapperConfig; import org.mapstruct.ap.util.Strings; +import static org.mapstruct.ap.util.Collections.first; + /** * Represents a mapping method with source and target type and the mappings between the properties of source and target * type. @@ -50,7 +54,6 @@ public class SourceMethod implements Method { private final Types typeUtils; private final TypeFactory typeFactory; - private final FormattingMessager messager; private final Type declaringMapper; private final ExecutableElement executable; @@ -61,10 +64,14 @@ public class SourceMethod implements Method { private final Accessibility accessibility; private final List exceptionTypes; private final MapperConfig config; + private final MappingOptions mappingOptions; + private final List prototypeMethods; - private Map> mappings; - private IterableMapping iterableMapping; - private MapMapping mapMapping; + private List sourceParameters; + + private List parameterNames; + + private List applicablePrototypeMethods; public static class Builder { @@ -80,6 +87,7 @@ public class SourceMethod implements Method { private TypeFactory typeFactory = null; private FormattingMessager messager = null; private MapperConfig mapperConfig = null; + private List prototypeMethods = Collections.emptyList(); public Builder() { } @@ -144,24 +152,27 @@ public class SourceMethod implements Method { return this; } - public SourceMethod createSourceMethod() { + public Builder setPrototypeMethods(List prototypeMethods) { + this.prototypeMethods = prototypeMethods; + return this; + } + + public SourceMethod buildSourceMethod() { SourceMethod sourceMethod = new SourceMethod( declaringMapper, executable, parameters, returnType, exceptionTypes, - mappings, - iterableMapping, - mapMapping, + new MappingOptions( mappings, iterableMapping, mapMapping ), typeUtils, typeFactory, - messager, - mapperConfig + mapperConfig, + prototypeMethods ); if ( mappings != null ) { - for ( Map.Entry> entry : sourceMethod.getMappings().entrySet() ) { + for ( Map.Entry> entry : mappings.entrySet() ) { for ( Mapping mapping : entry.getValue() ) { mapping.init( sourceMethod, messager, typeFactory ); } @@ -173,26 +184,24 @@ public class SourceMethod implements Method { @SuppressWarnings( "checkstyle:parameternumber" ) private SourceMethod( Type declaringMapper, ExecutableElement executable, List parameters, - Type returnType, List exceptionTypes, Map> mappings, - IterableMapping iterableMapping, MapMapping mapMapping, Types typeUtils, - TypeFactory typeFactory, FormattingMessager messager, MapperConfig config) { + Type returnType, List exceptionTypes, MappingOptions mappingOptions, Types typeUtils, + TypeFactory typeFactory, MapperConfig config, List prototypeMethods) { this.declaringMapper = declaringMapper; this.executable = executable; this.parameters = parameters; this.returnType = returnType; this.exceptionTypes = exceptionTypes; - this.mappings = mappings; - this.iterableMapping = iterableMapping; - this.mapMapping = mapMapping; this.accessibility = Accessibility.fromModifiers( executable.getModifiers() ); + this.mappingOptions = mappingOptions; + this.mappingTargetParameter = determineMappingTargetParameter( parameters ); this.targetTypeParameter = determineTargetTypeParameter( parameters ); this.typeUtils = typeUtils; this.typeFactory = typeFactory; - this.messager = messager; this.config = config; + this.prototypeMethods = prototypeMethods; } private Parameter determineMappingTargetParameter(Iterable parameters) { @@ -249,11 +258,13 @@ public class SourceMethod implements Method { */ @Override public List getSourceParameters() { - List sourceParameters = new ArrayList(); + if ( sourceParameters == null ) { + sourceParameters = new ArrayList(); - for ( Parameter parameter : parameters ) { - if ( !parameter.isMappingTarget() && !parameter.isTargetType() ) { - sourceParameters.add( parameter ); + for ( Parameter parameter : parameters ) { + if ( !parameter.isMappingTarget() && !parameter.isTargetType() ) { + sourceParameters.add( parameter ); + } } } @@ -262,10 +273,12 @@ public class SourceMethod implements Method { @Override public List getParameterNames() { - List parameterNames = new ArrayList( parameters.size() ); + if ( parameterNames == null ) { + parameterNames = new ArrayList( parameters.size() ); - for ( Parameter parameter : parameters ) { - parameterNames.add( parameter.getName() ); + for ( Parameter parameter : parameters ) { + parameterNames.add( parameter.getName() ); + } } return parameterNames; @@ -289,84 +302,31 @@ public class SourceMethod implements Method { return accessibility; } - /** - * @return the {@link Mapping}s configured for this method, keyed by target property name. Only for enum mapping - * methods a target will be mapped by several sources. - */ - public Map> getMappings() { - return mappings; - } - public Mapping getSingleMappingByTargetPropertyName(String targetPropertyName) { - List all = mappings.get( targetPropertyName ); - return all != null ? all.iterator().next() : null; - } - - public void setMappings(Map> mappings) { - this.mappings = mappings; - } - - public IterableMapping getIterableMapping() { - return iterableMapping; - } - - public void setIterableMapping(IterableMapping iterableMapping) { - this.iterableMapping = iterableMapping; - } - - public MapMapping getMapMapping() { - return mapMapping; - } - - public void setMapMapping(MapMapping mapMapping) { - this.mapMapping = mapMapping; + List all = mappingOptions.getMappings().get( targetPropertyName ); + return all != null ? first( all ) : null; } public boolean reverses(SourceMethod method) { return getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1 - && equals( getSourceParameters().iterator().next().getType(), method.getResultType() ) - && equals( getResultType(), method.getSourceParameters().iterator().next().getType() ); + && equals( first( getSourceParameters() ).getType(), method.getResultType() ) + && equals( getResultType(), first( method.getSourceParameters() ).getType() ); } public boolean isSame(SourceMethod method) { return getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1 - && equals( getSourceParameters().iterator().next().getType(), - method.getSourceParameters().iterator().next().getType()) + && equals( first( getSourceParameters() ).getType(), + first( method.getSourceParameters() ).getType() ) && equals( getResultType(), method.getResultType() ); } - public boolean isSimilar(SourceMethod method) { - Map test = new HashMap(); - - // check how many times a type occurs - for (Parameter sourceParam : method.getSourceParameters() ) { - Type sourceType = sourceParam.getType(); - if ( !test.containsKey( sourceType ) ) { - test.put( sourceType, 0 ); - } - increase( sourceType, test ); - } - - // check if this method also contains the same time each parameter type. - for (Parameter sourceParam : getSourceParameters() ) { - Type sourceType = sourceParam.getType(); - if ( !test.containsKey( sourceType ) ) { - // method contains a different parameter type than this - return false; - } - decrease( sourceType, test ); - } - - // now, if they match they should have the same source parameter types each - for ( Integer count : test.values() ) { - if ( count != 0 ) { - return false; - } - } - - // finally check the return type. - return equals( getResultType(), method.getResultType() ); + public boolean canInheritFrom(SourceMethod method) { + return isMapMapping() == method.isMapMapping() + && isIterableMapping() == method.isIterableMapping() + && isEnumMapping() == method.isEnumMapping() + && getResultType().isAssignableTo( method.getResultType() ) + && allParametersAreAssignable( getSourceParameters(), method.getSourceParameters() ); } @Override @@ -380,17 +340,17 @@ public class SourceMethod implements Method { } public boolean isIterableMapping() { - return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isIterableType() + return getSourceParameters().size() == 1 && first( getSourceParameters() ).getType().isIterableType() && getResultType().isIterableType(); } public boolean isMapMapping() { - return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isMapType() + return getSourceParameters().size() == 1 && first( getSourceParameters() ).getType().isMapType() && getResultType().isMapType(); } public boolean isEnumMapping() { - return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isEnumType() + return getSourceParameters().size() == 1 && first( getSourceParameters() ).getType().isEnumType() && getResultType().isEnumType(); } @@ -422,7 +382,7 @@ public class SourceMethod implements Method { public List getMappingBySourcePropertyName(String sourcePropertyName) { List mappingsOfSourceProperty = new ArrayList(); - for ( List mappingOfProperty : mappings.values() ) { + for ( List mappingOfProperty : mappingOptions.getMappings().values() ) { for ( Mapping mapping : mappingOfProperty ) { if ( isEnumMapping() ) { @@ -435,7 +395,7 @@ public class SourceMethod implements Method { // there can only be a mapping if there's only one entry for a source property, so: param.property. // There can be no mapping if there are more entries. So: param.property.property2 - if ( sourceEntries.size() == 1 && sourcePropertyName.equals( sourceEntries.get( 0 ).getName() ) ) { + if ( sourceEntries.size() == 1 && sourcePropertyName.equals( first( sourceEntries ).getName() ) ) { mappingsOfSourceProperty.add( mapping ); } } @@ -455,6 +415,45 @@ public class SourceMethod implements Method { return null; } + public List getApplicablePrototypeMethods() { + if ( applicablePrototypeMethods == null ) { + applicablePrototypeMethods = new ArrayList(); + + for ( SourceMethod prototype : prototypeMethods ) { + if ( canInheritFrom( prototype ) ) { + applicablePrototypeMethods.add( prototype ); + } + } + } + + return applicablePrototypeMethods; + } + + private static boolean allParametersAreAssignable(List fromParams, List toParams) { + if ( fromParams.size() == toParams.size() ) { + Set unaccountedToParams = new HashSet( toParams ); + + for ( Parameter fromParam : fromParams ) { + // each fromParam needs at least one match, and all toParam need to be accounted for at the end + boolean hasMatch = false; + for ( Parameter toParam : toParams ) { + if ( fromParam.getType().isAssignableTo( toParam.getType() ) ) { + unaccountedToParams.remove( toParam ); + hasMatch = true; + } + } + + if ( !hasMatch ) { + return false; + } + } + + return unaccountedToParams.isEmpty(); + } + + return false; + } + /** * Whether an implementation of this method must be generated or not. * @@ -494,57 +493,8 @@ public class SourceMethod implements Method { return exceptionTypes; } - /** - * Merges in all the mappings configured via the given inverse mapping method, giving the locally defined mappings - * precedence. - * @param inverseMethod - * @param templateMethod - */ - public void mergeWithInverseMappings(SourceMethod inverseMethod, SourceMethod templateMethod) { - Map> newMappings = new HashMap>(); - - if ( inverseMethod != null && !inverseMethod.getMappings().isEmpty() ) { - for ( List lmappings : inverseMethod.getMappings().values() ) { - for ( Mapping inverseMapping : lmappings ) { - Mapping reversed = inverseMapping.reverse( this, messager, typeFactory ); - if ( reversed != null ) { - List mappingsOfProperty = newMappings.get( reversed.getTargetName() ); - if ( mappingsOfProperty == null ) { - mappingsOfProperty = new ArrayList(); - newMappings.put( reversed.getTargetName(), mappingsOfProperty ); - } - mappingsOfProperty.add( reversed ); - } - } - } - } - - if ( templateMethod != null && !templateMethod.getMappings().isEmpty() ) { - for ( List lmappings : templateMethod.getMappings().values() ) { - for ( Mapping templateMapping : lmappings ) { - if ( templateMapping != null ) { - List mappingsOfProperty = newMappings.get( templateMapping.getTargetName() ); - if ( mappingsOfProperty == null ) { - mappingsOfProperty = new ArrayList(); - newMappings.put( templateMapping.getTargetName(), mappingsOfProperty ); - } - mappingsOfProperty.add( templateMapping ); - } - } - } - } - - if ( getMappings().isEmpty() ) { - // the mapping method is configuredByReverseMappingMethod, see SourceMethod#setMappings() - setMappings( newMappings ); - } - else { - // now add all of its own mappings - newMappings.putAll( getMappings() ); - getMappings().clear(); - // the mapping method is NOT configuredByReverseMappingMethod, - getMappings().putAll( newMappings ); - } + public MappingOptions getMappingOptions() { + return mappingOptions; } @Override @@ -552,17 +502,6 @@ public class SourceMethod implements Method { return executable.getModifiers().contains( Modifier.STATIC ); } - private void increase(Type key, Map test) { - Integer count = test.get( key ); - count++; - test.put( key, count ); - } - - private void decrease(Type key, Map test) { - Integer count = test.get( key ); - count--; - test.put( key, count ); - } /** * * @return the mapper config when this method needs to be implemented diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java index fd91e26df..344c45f1b 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java @@ -22,16 +22,18 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.mapstruct.ap.util.FormattingMessager; import javax.lang.model.element.ExecutableElement; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.TypeFactory; -import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Executables; +import org.mapstruct.ap.util.FormattingMessager; +import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Strings; +import static org.mapstruct.ap.util.Collections.first; + /** * This class describes the source side of a property mapping. *

@@ -287,4 +289,25 @@ public class SourceReference { } } + + /** + * Creates a copy of this reference, which is adapted to the given method + * + * @param the method to adapt the copy to + */ + public SourceReference copyForInheritanceTo(SourceMethod method) { + List replacementParamCandidates = new ArrayList(); + for ( Parameter sourceParam : method.getSourceParameters() ) { + if ( sourceParam.getType().isAssignableTo( parameter.getType() ) ) { + replacementParamCandidates.add( sourceParam ); + } + } + + Parameter replacement = parameter; + if ( replacementParamCandidates.size() == 1 ) { + replacement = first( replacementParamCandidates ); + } + + return new SourceReference( replacement, propertyEntries, isValid ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/model/source/selector/InheritanceSelector.java index ea889a622..704ecddf5 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/selector/InheritanceSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/selector/InheritanceSelector.java @@ -25,6 +25,8 @@ import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.source.Method; +import static org.mapstruct.ap.util.Collections.first; + /** * Selects on inheritance distance, e.g. the amount of inheritance steps from the parameter type. * @@ -50,7 +52,7 @@ public class InheritanceSelector implements MethodSelector { // find the methods with the minimum distance regarding getParameter getParameter type for ( T method : methods ) { - Parameter singleSourceParam = method.getSourceParameters().iterator().next(); + Parameter singleSourceParam = first( method.getSourceParameters() ); int sourceTypeDistance = sourceType.distanceTo( singleSourceParam.getType() ); bestMatchingSourceTypeDistance = diff --git a/processor/src/main/java/org/mapstruct/ap/prism/MappingInheritanceStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/prism/MappingInheritanceStrategyPrism.java new file mode 100644 index 000000000..9ce415662 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/prism/MappingInheritanceStrategyPrism.java @@ -0,0 +1,31 @@ +/** + * Copyright 2012-2015 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.prism; + + +/** + * Prism for the enum {@link org.mapstruct.MappingInheritanceStrategy} + * + * @author Andreas Gudian + */ +public enum MappingInheritanceStrategyPrism { + EXPLICIT, + AUTO_INHERIT_FROM_CONFIG, + DEFAULT; +} diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index 3844c536e..ee5829956 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.SortedSet; import java.util.TreeSet; -import org.mapstruct.ap.util.FormattingMessager; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; @@ -46,6 +45,7 @@ import org.mapstruct.ap.model.MappingBuilderContext; import org.mapstruct.ap.model.MappingMethod; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.TypeFactory; +import org.mapstruct.ap.model.source.MappingOptions; import org.mapstruct.ap.model.source.SourceMethod; import org.mapstruct.ap.option.Options; import org.mapstruct.ap.prism.DecoratedWithPrism; @@ -53,11 +53,16 @@ import org.mapstruct.ap.prism.InheritConfigurationPrism; import org.mapstruct.ap.prism.InheritInverseConfigurationPrism; import org.mapstruct.ap.prism.MapperPrism; import org.mapstruct.ap.processor.creation.MappingResolverImpl; -import org.mapstruct.ap.util.Message; +import org.mapstruct.ap.util.FormattingMessager; import org.mapstruct.ap.util.MapperConfig; +import org.mapstruct.ap.util.Message; import org.mapstruct.ap.util.Strings; import org.mapstruct.ap.version.VersionInformation; +import static org.mapstruct.ap.prism.MappingInheritanceStrategyPrism.AUTO_INHERIT_FROM_CONFIG; +import static org.mapstruct.ap.util.Collections.first; +import static org.mapstruct.ap.util.Collections.join; + /** * A {@link ModelElementProcessor} which creates a {@link Mapper} from the given * list of {@link SourceMethod}s. @@ -83,7 +88,8 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences = initReferencedMappers( mapperTypeElement ); + MapperConfig mapperConfig = MapperConfig.getInstanceOn( mapperTypeElement ); + List mapperReferences = initReferencedMappers( mapperTypeElement, mapperConfig ); MappingBuilderContext ctx = new MappingBuilderContext( typeFactory, @@ -104,7 +110,7 @@ public class MapperCreationProcessor implements ModelElementProcessor initReferencedMappers(TypeElement element) { + private List initReferencedMappers(TypeElement element, MapperConfig mapperConfig) { List result = new LinkedList(); List variableNames = new LinkedList(); - MapperConfig mapperPrism = MapperConfig.getInstanceOn( element ); - - for ( TypeMirror usedMapper : mapperPrism.uses() ) { + for ( TypeMirror usedMapper : mapperConfig.uses() ) { DefaultMapperReference mapperReference = DefaultMapperReference.getInstance( typeFactory.getType( usedMapper ), MapperPrism.getInstanceOn( typeUtils.asElement( usedMapper ) ) != null, @@ -133,9 +137,9 @@ public class MapperCreationProcessor implements ModelElementProcessor methods) { + private Mapper getMapper(TypeElement element, MapperConfig mapperConfig, List methods) { List mapperReferences = mappingContext.getMapperReferences(); - List mappingMethods = getMappingMethods( methods ); + List mappingMethods = getMappingMethods( mapperConfig, methods ); mappingMethods.addAll( mappingContext.getUsedVirtualMappings() ); mappingMethods.addAll( mappingContext.getMappingsToGenerate() ); @@ -194,7 +198,7 @@ public class MapperCreationProcessor implements ModelElementProcessor getMappingMethods(List methods) { + private List getMappingMethods(MapperConfig mapperConfig, List methods) { List mappingMethods = new ArrayList(); for ( SourceMethod method : methods ) { @@ -241,29 +245,23 @@ public class MapperCreationProcessor implements ModelElementProcessor() ); + + MappingOptions mappingOptions = method.getMappingOptions(); boolean hasFactoryMethod = false; + if ( method.isIterableMapping() ) { IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder(); - if ( method.getIterableMapping() == null ) { - if ( inverseMappingMethod != null && inverseMappingMethod.getIterableMapping() != null ) { - method.setIterableMapping( inverseMappingMethod.getIterableMapping() ); - } - else if ( templateMappingMethod != null && templateMappingMethod.getIterableMapping() != null ) { - method.setIterableMapping( templateMappingMethod.getIterableMapping() ); - } - } String dateFormat = null; List qualifiers = null; TypeMirror qualifyingElementTargetType = null; - if ( method.getIterableMapping() != null ) { - dateFormat = method.getIterableMapping().getDateFormat(); - qualifiers = method.getIterableMapping().getQualifiers(); - qualifyingElementTargetType = method.getIterableMapping().getQualifyingElementTargetType(); + if ( mappingOptions.getIterableMapping() != null ) { + dateFormat = mappingOptions.getIterableMapping().getDateFormat(); + qualifiers = mappingOptions.getIterableMapping().getQualifiers(); + qualifyingElementTargetType = mappingOptions.getIterableMapping().getQualifyingElementTargetType(); } IterableMappingMethod iterableMappingMethod = builder @@ -281,27 +279,19 @@ public class MapperCreationProcessor implements ModelElementProcessor keyQualifiers = null; List valueQualifiers = null; TypeMirror keyQualifyingTargetType = null; TypeMirror valueQualifyingTargetType = null; - if ( method.getMapMapping() != null ) { - keyDateFormat = method.getMapMapping().getKeyFormat(); - valueDateFormat = method.getMapMapping().getValueFormat(); - keyQualifiers = method.getMapMapping().getKeyQualifiers(); - valueQualifiers = method.getMapMapping().getValueQualifiers(); - keyQualifyingTargetType = method.getMapMapping().getKeyQualifyingTargetType(); - valueQualifyingTargetType = method.getMapMapping().getValueQualifyingTargetType(); + if ( mappingOptions.getMapMapping() != null ) { + keyDateFormat = mappingOptions.getMapMapping().getKeyFormat(); + valueDateFormat = mappingOptions.getMapMapping().getValueFormat(); + keyQualifiers = mappingOptions.getMapMapping().getKeyQualifiers(); + valueQualifiers = mappingOptions.getMapMapping().getValueQualifiers(); + keyQualifyingTargetType = mappingOptions.getMapMapping().getKeyQualifyingTargetType(); + valueQualifyingTargetType = mappingOptions.getMapMapping().getValueQualifyingTargetType(); } MapMappingMethod mapMappingMethod = builder @@ -321,7 +311,6 @@ public class MapperCreationProcessor implements ModelElementProcessor availableMethods, List initializingMethods) { + if ( initializingMethods.contains( method ) ) { + // cycle detected + + initializingMethods.add( method ); + + messager.printMessage( + method.getExecutable(), + Message.INHERITCONFIGURATION_CYCLE, + Strings.join( initializingMethods, " -> " ) ); + return; + } + + initializingMethods.add( method ); + + MappingOptions mappingOptions = method.getMappingOptions(); + List applicablePrototypeMethods = method.getApplicablePrototypeMethods(); + + MappingOptions inverseMappingOptions = + getInverseMappingOptions( availableMethods, method, initializingMethods, mapperConfig ); + + MappingOptions templateMappingOptions = + getTemplateMappingOptions( + join( availableMethods, applicablePrototypeMethods ), + method, + initializingMethods, + mapperConfig ); + + if ( templateMappingOptions != null ) { + mappingOptions.applyInheritedOptions( templateMappingOptions, false, method, messager, typeFactory ); + } + else if ( inverseMappingOptions != null ) { + mappingOptions.applyInheritedOptions( inverseMappingOptions, true, method, messager, typeFactory ); + } + else if ( mapperConfig.getMappingInheritanceStrategy() == AUTO_INHERIT_FROM_CONFIG ) { + if ( applicablePrototypeMethods.size() == 1 ) { + mappingOptions.applyInheritedOptions( + first( applicablePrototypeMethods ).getMappingOptions(), + false, + method, + messager, + typeFactory ); + } + else if ( applicablePrototypeMethods.size() > 1 ) { + messager.printMessage( + method.getExecutable(), + Message.INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH, + Strings.join( applicablePrototypeMethods, ", " ) ); + } + } + + mappingOptions.markAsFullyInitialized(); + } + private void reportErrorIfNoImplementationTypeIsRegisteredForInterfaceReturnType(SourceMethod method) { if ( method.getReturnType().getTypeMirror().getKind() != TypeKind.VOID && method.getReturnType().isInterface() && @@ -365,12 +408,13 @@ public class MapperCreationProcessor implements ModelElementProcessor rawMethods, SourceMethod method) { - SourceMethod result = null; + private MappingOptions getInverseMappingOptions(List rawMethods, SourceMethod method, + List initializingMethods, MapperConfig mapperConfig) { + SourceMethod resultMethod = null; InheritInverseConfigurationPrism reversePrism = InheritInverseConfigurationPrism.getInstanceOn( method.getExecutable() ); @@ -389,10 +433,10 @@ public class MapperCreationProcessor implements ModelElementProcessor 1 ) { reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, reversePrism ); } - if ( result == null ) { + if ( resultMethod == null ) { reportErrorWhenAmbigousReverseMapping( candidates, method, reversePrism ); } } - - if ( result != null ) { - reportErrorIfInverseMethodHasInheritAnnotation( result, method, reversePrism ); - } - } - return result; + + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); } - /** - * Returns the configuring forward method in case the given method is annotated with - * {@code @InheritConfiguration} and exactly one such configuring method can unambiguously be selected (as - * per the source/target type and optionally the name given via {@code @InheritConfiguration}). - * - * The method cannot be marked forward mapping itself (hence 'ohter'). And neither can it contain an - * {@code @InheritReverseConfiguration} + private MappingOptions extractInitializedOptions(SourceMethod resultMethod, + List rawMethods, + MapperConfig mapperConfig, + List initializingMethods) { + if ( resultMethod != null ) { + if ( !resultMethod.getMappingOptions().isFullyInitialized() ) { + mergeInheritedOptions( resultMethod, mapperConfig, rawMethods, initializingMethods ); + } + + return resultMethod.getMappingOptions(); + } + + return null; + } + + /** + * Returns the configuring forward method's options in case the given method is annotated with + * {@code @InheritConfiguration} and exactly one such configuring method can unambiguously be selected (as per the + * source/target type and optionally the name given via {@code @InheritConfiguration}). The method cannot be marked + * forward mapping itself (hence 'ohter'). And neither can it contain an {@code @InheritReverseConfiguration} */ - private SourceMethod getTemplateMappingMethod(List rawMethods, SourceMethod method) { - SourceMethod result = null; + private MappingOptions getTemplateMappingOptions(List rawMethods, SourceMethod method, + List initializingMethods, + MapperConfig mapperConfig) { + SourceMethod resultMethod = null; InheritConfigurationPrism forwardPrism = InheritConfigurationPrism.getInstanceOn( method.getExecutable() ); @@ -445,11 +500,10 @@ public class MapperCreationProcessor implements ModelElementProcessor candidates = new ArrayList(); for ( SourceMethod oneMethod : rawMethods ) { // method must be similar but not equal - if ( oneMethod.isSimilar( method ) && !( oneMethod.equals( method ) ) ) { + if ( method.canInheritFrom( oneMethod ) && !( oneMethod.equals( method ) ) ) { candidates.add( oneMethod ); } } @@ -457,14 +511,15 @@ public class MapperCreationProcessor implements ModelElementProcessor 1 ) { @@ -478,23 +533,19 @@ public class MapperCreationProcessor implements ModelElementProcessor 1 ) { reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, forwardPrism ); } - if ( result == null ) { + if ( resultMethod == null ) { reportErrorWhenAmbigousMapping( candidates, method, forwardPrism ); } } - - if ( result != null ) { - reportErrorIfMethodHasAnnotation( result, method, forwardPrism ); - } - } - return result; + + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); } private void reportErrorWhenInheritForwardAlsoHasInheritReverseMapping(SourceMethod method) { @@ -507,34 +558,6 @@ public class MapperCreationProcessor implements ModelElementProcessor candidates, SourceMethod method, InheritInverseConfigurationPrism reversePrism) { @@ -570,7 +593,7 @@ public class MapperCreationProcessor implements ModelElementProcessor candidates, SourceMethod method, InheritConfigurationPrism prism) { diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java index 98ff02bc8..a29b2d0de 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -18,14 +18,12 @@ */ package org.mapstruct.ap.processor; -import static org.mapstruct.ap.util.Executables.getAllEnclosedExecutableElements; - import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.mapstruct.ap.util.FormattingMessager; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -48,8 +46,11 @@ import org.mapstruct.ap.prism.MapMappingPrism; import org.mapstruct.ap.prism.MappingPrism; import org.mapstruct.ap.prism.MappingsPrism; import org.mapstruct.ap.util.AnnotationProcessingException; -import org.mapstruct.ap.util.Message; +import org.mapstruct.ap.util.FormattingMessager; import org.mapstruct.ap.util.MapperConfig; +import org.mapstruct.ap.util.Message; + +import static org.mapstruct.ap.util.Executables.getAllEnclosedExecutableElements; /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s @@ -72,7 +73,19 @@ public class MethodRetrievalProcessor implements ModelElementProcessor prototypeMethods = + retrievePrototypeMethods( mapperConfig.getMapperConfigMirror(), mapperConfig ); + return retrieveMethods( mapperTypeElement, mapperTypeElement, mapperConfig, prototypeMethods ); } @Override @@ -80,19 +93,60 @@ public class MethodRetrievalProcessor implements ModelElementProcessor retrievePrototypeMethods(TypeMirror typeMirror, MapperConfig mapperConfig) { + if ( typeMirror == null || typeMirror.getKind() == TypeKind.VOID ) { + return Collections.emptyList(); + } + + TypeElement typeElement = asTypeElement( typeMirror ); + List methods = new ArrayList(); + for ( ExecutableElement executable : getAllEnclosedExecutableElements( elementUtils, typeElement ) ) { + + ExecutableType methodType = typeFactory.getMethodType( typeElement, executable ); + List parameters = typeFactory.getParameters( methodType, executable ); + boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters ); + + // prototype methods don't have prototypes themselves + List prototypeMethods = Collections.emptyList(); + + SourceMethod method = + getMethodRequiringImplementation( + methodType, + executable, + parameters, + containsTargetTypeParameter, + mapperConfig, + prototypeMethods ); + + if ( method != null ) { + methods.add( method ); + } + } + + return methods; + } + /** * Retrieves the mapping methods declared by the given mapper type. * * @param usedMapper The type of interest (either the mapper to implement or a used mapper via @uses annotation) * @param mapperToImplement the top level type (mapper) that requires implementation - * + * @param mapperConfig the mapper config + * @param prototypeMethods prototype methods defined in mapper config type * @return All mapping methods declared by the given type */ - private List retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement) { + private List retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement, + MapperConfig mapperConfig, List prototypeMethods) { List methods = new ArrayList(); for ( ExecutableElement executable : getAllEnclosedExecutableElements( elementUtils, usedMapper ) ) { - SourceMethod method = getMethod( usedMapper, executable, mapperToImplement ); + SourceMethod method = getMethod( + usedMapper, + executable, + mapperToImplement, + mapperConfig, + prototypeMethods ); + if ( method != null ) { methods.add( method ); } @@ -100,15 +154,12 @@ public class MethodRetrievalProcessor implements ModelElementProcessor prototypeMethods) { ExecutableType methodType = typeFactory.getMethodType( usedMapper, method ); List parameters = typeFactory.getParameters( methodType, method ); @@ -134,7 +187,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters, boolean containsTargetTypeParameter, - TypeElement mapperToImplement) { + MapperConfig mapperConfig, + List prototypeMethods) { Type returnType = typeFactory.getReturnType( methodType ); List exceptionTypes = typeFactory.getThrownTypes( methodType ); List sourceParameters = extractSourceParameters( parameters ); @@ -181,8 +236,9 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters) { diff --git a/processor/src/main/java/org/mapstruct/ap/util/Collections.java b/processor/src/main/java/org/mapstruct/ap/util/Collections.java index bbefa270f..e41e4715b 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Collections.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Collections.java @@ -72,4 +72,17 @@ public class Collections { return set; } + + public static T first(Collection collection) { + return collection.iterator().next(); + } + + public static List join(List a, List b) { + List result = new ArrayList( a.size() + b.size() ); + + result.addAll( a ); + result.addAll( b ); + + return result; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/util/MapperConfig.java b/processor/src/main/java/org/mapstruct/ap/util/MapperConfig.java index e09a5b19e..c6fc33e43 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/MapperConfig.java +++ b/processor/src/main/java/org/mapstruct/ap/util/MapperConfig.java @@ -33,11 +33,10 @@ import org.mapstruct.ap.option.ReportingPolicy; import org.mapstruct.ap.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.prism.MapperConfigPrism; import org.mapstruct.ap.prism.MapperPrism; +import org.mapstruct.ap.prism.MappingInheritanceStrategyPrism; import org.mapstruct.ap.prism.NullValueMappingPrism; import org.mapstruct.ap.prism.NullValueMappingStrategyPrism; -import static org.mapstruct.ap.prism.CollectionMappingStrategyPrism.valueOf; - /** * Class decorating the {@link MapperPrism} with the 'default' configuration. * @@ -58,10 +57,6 @@ public class MapperConfig { return new MapperConfig( MapperPrism.getInstanceOn( e ) ); } - public static MapperConfig getInstance(AnnotationMirror mirror) { - return new MapperConfig( MapperPrism.getInstance( mirror ) ); - } - private MapperConfig(MapperPrism mapperPrism) { this.mapperPrism = mapperPrism; TypeMirror typeMirror = mapperPrism.config(); @@ -100,7 +95,8 @@ public class MapperConfig { } public CollectionMappingStrategyPrism getCollectionMappingStrategy() { - CollectionMappingStrategyPrism mapperPolicy = valueOf( mapperPrism.collectionMappingStrategy() ); + CollectionMappingStrategyPrism mapperPolicy = + CollectionMappingStrategyPrism.valueOf( mapperPrism.collectionMappingStrategy() ); if ( mapperPolicy != CollectionMappingStrategyPrism.DEFAULT ) { // it is not the default mapper configuration, so return the mapper configured value @@ -108,7 +104,8 @@ public class MapperConfig { } else if ( mapperConfigPrism != null ) { // try the config mapper configuration - CollectionMappingStrategyPrism configPolicy = valueOf( mapperConfigPrism.collectionMappingStrategy() ); + CollectionMappingStrategyPrism configPolicy = + CollectionMappingStrategyPrism.valueOf( mapperConfigPrism.collectionMappingStrategy() ); if ( configPolicy != CollectionMappingStrategyPrism.DEFAULT ) { // its not the default configuration, so return the mapper config configured value return configPolicy; @@ -118,6 +115,24 @@ public class MapperConfig { return CollectionMappingStrategyPrism.ACCESSOR_ONLY; } + public MappingInheritanceStrategyPrism getMappingInheritanceStrategy() { + MappingInheritanceStrategyPrism mapperPolicy = + MappingInheritanceStrategyPrism.valueOf( mapperPrism.mappingInheritanceStrategy() ); + + if ( mapperPolicy != MappingInheritanceStrategyPrism.DEFAULT ) { + return mapperPolicy; + } + else if ( mapperConfigPrism != null ) { + MappingInheritanceStrategyPrism configPolicy = + MappingInheritanceStrategyPrism.valueOf( mapperConfigPrism.mappingInheritanceStrategy() ); + if ( configPolicy != MappingInheritanceStrategyPrism.DEFAULT ) { + return configPolicy; + } + } + + return MappingInheritanceStrategyPrism.EXPLICIT; + } + public boolean isMapToDefault(NullValueMappingPrism mapNullToDefault) { // check on method level @@ -165,6 +180,10 @@ public class MapperConfig { } } + public TypeMirror getMapperConfigMirror() { + return mapperPrism.config(); + } + public boolean isValid() { return mapperPrism.isValid; } diff --git a/processor/src/main/java/org/mapstruct/ap/util/Message.java b/processor/src/main/java/org/mapstruct/ap/util/Message.java index 6c61a03e1..8db9c5086 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Message.java @@ -86,18 +86,16 @@ public enum Message { RETRIEVAL_NON_ENUM_TO_ENUM( "Can't generate mapping method from non-enum type to enum type." ), INHERITCONFIGURATION_BOTH( "Method cannot be annotated with both a @InheritConfiguration and @InheritInverseConfiguration." ), - INHERITINVERSECONFIGURATION_REFERENCE_HAS_INVERSE( "Resolved inverse mapping method %s() should not carry the @InheritInverseConfiguration annotation itself." ), - INHERITINVERSECONFIGURATION_REFERENCE_HAS_FORWARD( "Resolved inverse mapping method %s() should not carry the @InheritConfiguration annotation." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ), INHERITINVERSECONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s()." ), INHERITINVERSECONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ), - INHERITCONFIGURATION_REFERENCE_HAS_FORWARD( "Resolved mapping method %s() should not carry the @InheritConfiguration annotation itself." ), - INHERITCONFIGURATION_REFERENCE_HAS_INVERSE( "Resolved mapping method %s() should not carry the @InheritInverseConfiguration annotation." ), INHERITCONFIGURATION_DUPLICATES( "Several matching methods exist: %s(). Specify a name explicitly." ), INHERITCONFIGURATION_INVALIDNAME( "None of the candidates %s() matches given name: \"%s\"." ), INHERITCONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s()." ), - INHERITCONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ); + INHERITCONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ), + INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH( "More than one configuration prototype method is applicable. Use @InheritConfiguration to select one of them explicitly: %s." ), + INHERITCONFIGURATION_CYCLE( "Cycle detected while evaluating inherited configurations. Inheritance path: %s" ); // CHECKSTYLE:ON private final String description; diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedConfig.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedConfig.java new file mode 100644 index 000000000..12c63ade8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedConfig.java @@ -0,0 +1,40 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; + +/** + * @author Andreas Gudian + */ +@MapperConfig( + mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG, + unmappedTargetPolicy = ReportingPolicy.ERROR +) +public interface AutoInheritedConfig { + @Mappings( { + @Mapping( target = "primaryKey", source = "id" ), + @Mapping( target = "auditTrail", ignore = true ) + } ) + BaseVehicleEntity baseDtoToEntity(BaseVehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedDriverConfig.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedDriverConfig.java new file mode 100644 index 000000000..6e2e1764e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/AutoInheritedDriverConfig.java @@ -0,0 +1,41 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; + +/** + * @author Andreas Gudian + */ +@MapperConfig( + mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG, + unmappedTargetPolicy = ReportingPolicy.ERROR +) +public interface AutoInheritedDriverConfig { + @Mappings( { + @Mapping( target = "primaryKey", source = "dto.id" ), + @Mapping( target = "auditTrail", ignore = true ), + @Mapping( target = "driverName", source = "drv.name" ) + } ) + CarWithDriverEntity baseDtoToEntity(DriverDto drv, BaseVehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleDto.java new file mode 100644 index 000000000..17f725c22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleDto.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +/** + * @author Andreas Gudian + * + */ +public abstract class BaseVehicleDto { + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleEntity.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleEntity.java new file mode 100644 index 000000000..a79024afb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/BaseVehicleEntity.java @@ -0,0 +1,44 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +/** + * @author Andreas Gudian + * + */ +public abstract class BaseVehicleEntity { + private long primaryKey; + private String auditTrail; + + public long getPrimaryKey() { + return primaryKey; + } + + public void setPrimaryKey(long primaryKey) { + this.primaryKey = primaryKey; + } + + public String getAuditTrail() { + return auditTrail; + } + + public void setAuditTrail(String auditTrail) { + this.auditTrail = auditTrail; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarDto.java new file mode 100644 index 000000000..9928a3e00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarDto.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +/** + * @author Andreas Gudian + * + */ +public class CarDto extends BaseVehicleDto { + private String colour; + + public String getColour() { + return colour; + } + + public void setColour(String colour) { + this.colour = colour; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarEntity.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarEntity.java new file mode 100644 index 000000000..080d93958 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarEntity.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +/** + * @author Andreas Gudian + * + */ +public class CarEntity extends BaseVehicleEntity { + private String color; + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperErroneouslyAnnotated2.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarMapperWithAutoInheritance.java similarity index 52% rename from processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperErroneouslyAnnotated2.java rename to processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarMapperWithAutoInheritance.java index 5c11822ce..6f224352b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperErroneouslyAnnotated2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarMapperWithAutoInheritance.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.test.template; +package org.mapstruct.ap.test.inheritfromconfig; import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritInverseConfiguration; @@ -27,28 +27,30 @@ import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; /** - * @author Sjaak Derksen + * @author Andreas Gudian + * */ -@Mapper -public interface SourceTargetMapperErroneouslyAnnotated2 { +@Mapper( + config = AutoInheritedConfig.class +) +public interface CarMapperWithAutoInheritance { + CarMapperWithAutoInheritance INSTANCE = Mappers.getMapper( CarMapperWithAutoInheritance.class ); - SourceTargetMapperErroneouslyAnnotated2 INSTANCE = - Mappers.getMapper( SourceTargetMapperErroneouslyAnnotated2.class ); + @Mapping( target = "color", source = "colour" ) + CarEntity toCarEntity(CarDto carDto); - @Mappings({ - @Mapping(target = "stringPropY", source = "stringPropX"), - @Mapping(target = "integerPropY", source = "integerPropX"), - @Mapping(target = "nestedResultProp", source = "nestedSourceProp.nested"), - @Mapping(target = "constantProp", constant = "constant"), - @Mapping(target = "expressionProp", expression = "java(\"expression\")") - }) - Target forwardCreate(Source source); + @InheritInverseConfiguration( name = "toCarEntity" ) + CarDto toCarDto(CarEntity entity); - @InheritInverseConfiguration(name = "forwardCreate") - @Mapping(target = "nestedSourceProp", ignore = true) - Source forwardCreate(Target source); + @Mappings( { + @Mapping( target = "color", source = "colour" ), + @Mapping( target = "auditTrail", constant = "fixed" ) + } ) + CarEntity toCarEntityWithFixedAuditTrail(CarDto carDto); - @InheritConfiguration( name = "forwardCreate" ) - void forwardUpdate(Target source, @MappingTarget Source target); + @Mapping( target = "color", source = "colour" ) + void intoCarEntityOnItsOwn(CarDto carDto, @MappingTarget CarEntity entity); + @InheritConfiguration( name = "toCarEntity" ) + void intoCarEntity(CarDto carDto, @MappingTarget CarEntity entity); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarMapperWithExplicitInheritance.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarMapperWithExplicitInheritance.java new file mode 100644 index 000000000..92d831627 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarMapperWithExplicitInheritance.java @@ -0,0 +1,49 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + * + */ +@Mapper( + config = AutoInheritedConfig.class, + mappingInheritanceStrategy = MappingInheritanceStrategy.EXPLICIT +) +public interface CarMapperWithExplicitInheritance { + CarMapperWithExplicitInheritance INSTANCE = Mappers.getMapper( CarMapperWithExplicitInheritance.class ); + + @InheritConfiguration( name = "baseDtoToEntity" ) + @Mapping( target = "color", source = "colour" ) + CarEntity toCarEntity(CarDto carDto); + + @InheritInverseConfiguration( name = "toCarEntity" ) + CarDto toCarDto(CarEntity entity); + + @InheritConfiguration( name = "toCarEntity" ) + @Mapping( target = "auditTrail", constant = "fixed" ) + CarEntity toCarEntityWithFixedAuditTrail(CarDto carDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverEntity.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverEntity.java new file mode 100644 index 000000000..ad41ed9b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverEntity.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +/** + * @author Andreas Gudian + * + */ +public class CarWithDriverEntity extends CarEntity { + private String driverName; + + public String getDriverName() { + return driverName; + } + + public void setDriverName(String driverName) { + this.driverName = driverName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverMapperWithAutoInheritance.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverMapperWithAutoInheritance.java new file mode 100644 index 000000000..09c067800 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/CarWithDriverMapperWithAutoInheritance.java @@ -0,0 +1,37 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + * + */ +@Mapper( + config = AutoInheritedDriverConfig.class +) +public interface CarWithDriverMapperWithAutoInheritance { + CarWithDriverMapperWithAutoInheritance INSTANCE = Mappers.getMapper( CarWithDriverMapperWithAutoInheritance.class ); + + @Mapping( target = "color", source = "carDto.colour" ) + CarWithDriverEntity toCarWithDriverEntity(CarDto carDto, DriverDto driverDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/DriverDto.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/DriverDto.java new file mode 100644 index 000000000..eefb6ef24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/DriverDto.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +/** + * @author Andreas Gudian + * + */ +public class DriverDto { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Config.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Config.java new file mode 100644 index 000000000..4cb6b5015 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Config.java @@ -0,0 +1,45 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; + +/** + * Leads to ambiguous prototype methods error. + * + * @author Andreas Gudian + */ +@MapperConfig( + mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG, + unmappedTargetPolicy = ReportingPolicy.WARN +) +public interface Erroneous1Config { + @Mappings( { + @Mapping( target = "primaryKey", source = "id" ), + @Mapping( target = "auditTrail", ignore = true ) + } ) + BaseVehicleEntity baseDtoToEntity(BaseVehicleDto dto); + + @Mapping( target = "primaryKey", ignore = true ) + BaseVehicleEntity anythingToEntity(Object anyting); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Mapper.java new file mode 100644 index 000000000..ab33131bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous1Mapper.java @@ -0,0 +1,44 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + * + */ +@Mapper( + config = Erroneous1Config.class +) +public interface Erroneous1Mapper { + Erroneous1Mapper INSTANCE = Mappers.getMapper( Erroneous1Mapper.class ); + + @Mapping( target = "color", source = "colour" ) + CarEntity toCarEntity(CarDto carDto); + + @Mappings( { + @Mapping( target = "color", source = "colour" ), + @Mapping( target = "auditTrail", constant = "fixed" ) + } ) + CarEntity toCarEntityWithFixedAuditTrail(CarDto carDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/reverse/SourceTargetMapperErroneouslyAnnotated2.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous2Mapper.java similarity index 55% rename from processor/src/test/java/org/mapstruct/ap/test/reverse/SourceTargetMapperErroneouslyAnnotated2.java rename to processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous2Mapper.java index 81d309fa7..35b990db0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/reverse/SourceTargetMapperErroneouslyAnnotated2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/Erroneous2Mapper.java @@ -16,34 +16,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.test.reverse; +package org.mapstruct.ap.test.inheritfromconfig; import org.mapstruct.InheritConfiguration; -import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; /** - * @author Sjaak Derksen + * @author Andreas Gudian + * */ -@Mapper -public interface SourceTargetMapperErroneouslyAnnotated2 { +@Mapper( + config = AutoInheritedConfig.class +) +public interface Erroneous2Mapper { + Erroneous2Mapper INSTANCE = Mappers.getMapper( Erroneous2Mapper.class ); - SourceTargetMapperErroneouslyAnnotated2 INSTANCE - = Mappers.getMapper( SourceTargetMapperErroneouslyAnnotated2.class ); + @InheritConfiguration( name = "toCarEntity2" ) + CarEntity toCarEntity1(CarDto carDto); - @Mappings({ - @Mapping(source = "stringPropX", target = "stringPropY"), - @Mapping(source = "integerPropX", target = "integerPropY"), - @Mapping(source = "propertyToIgnoreDownstream", target = "propertyNotToIgnoreUpstream") - }) - Target forward(Source source); + @InheritConfiguration( name = "toCarEntity3" ) + CarEntity toCarEntity2(CarDto carDto); - @InheritConfiguration(name = "forward2") - Source forward2(Target target); - - @InheritInverseConfiguration(name = "forward2") - Target reverse(Source source); + @InheritConfiguration( name = "toCarEntity1" ) + @Mappings( { + @Mapping( target = "color", ignore = true ), + @Mapping( target = "auditTrail", ignore = true ), + @Mapping( target = "primaryKey", ignore = true ) + } ) + void toCarEntity3(CarDto carDto, @MappingTarget CarEntity entity); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java new file mode 100644 index 000000000..df10a4bf0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java @@ -0,0 +1,216 @@ +/** + * Copyright 2012-2015 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.inheritfromconfig; + +import javax.tools.Diagnostic.Kind; + +import org.junit.Test; +import org.junit.runner.RunWith; +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.fest.assertions.Assertions.assertThat; + +/** + * @author Andreas Gudian + * + */ +@RunWith( AnnotationProcessorTestRunner.class ) +@WithClasses( { + BaseVehicleDto.class, + BaseVehicleEntity.class, + CarDto.class, + CarEntity.class, + CarMapperWithAutoInheritance.class, + CarMapperWithExplicitInheritance.class, + AutoInheritedConfig.class } ) +@IssueKey( "168" ) +public class InheritFromConfigTest { + + @Test + public void autoInheritedMappingIsApplied() { + CarDto carDto = newTestDto(); + + CarEntity carEntity = CarMapperWithAutoInheritance.INSTANCE.toCarEntity( carDto ); + + assertEntity( carEntity ); + } + + @Test + public void autoInheritedMappingIsAppliedForMappingTarget() { + CarDto carDto = newTestDto(); + CarEntity carEntity = new CarEntity(); + + CarMapperWithAutoInheritance.INSTANCE.intoCarEntityOnItsOwn( carDto, carEntity ); + + assertEntity( carEntity ); + } + + @Test + public void autoInheritedMappingIsAppliedForMappingTargetWithTwoStepInheritance() { + CarDto carDto = newTestDto(); + CarEntity carEntity = new CarEntity(); + + CarMapperWithAutoInheritance.INSTANCE.intoCarEntity( carDto, carEntity ); + + assertEntity( carEntity ); + } + + private void assertEntity(CarEntity carEntity) { + assertThat( carEntity.getColor() ).isEqualTo( "red" ); + assertThat( carEntity.getPrimaryKey() ).isEqualTo( 42L ); + assertThat( carEntity.getAuditTrail() ).isNull(); + } + + private CarDto newTestDto() { + CarDto carDto = new CarDto(); + carDto.setColour( "red" ); + carDto.setId( 42L ); + return carDto; + } + + @Test + public void autoInheritedMappingIsOverriddenAtMethodLevel() { + CarDto carDto = newTestDto(); + + CarEntity carEntity = CarMapperWithAutoInheritance.INSTANCE.toCarEntityWithFixedAuditTrail( carDto ); + + assertThat( carEntity.getColor() ).isEqualTo( "red" ); + assertThat( carEntity.getPrimaryKey() ).isEqualTo( 42L ); + assertThat( carEntity.getAuditTrail() ).isEqualTo( "fixed" ); + } + + @Test + public void autoInheritedMappingIsAppliedInReverse() { + CarEntity carEntity = new CarEntity(); + carEntity.setColor( "red" ); + carEntity.setPrimaryKey( 42L ); + + CarDto carDto = CarMapperWithAutoInheritance.INSTANCE.toCarDto( carEntity ); + + assertThat( carDto.getColour() ).isEqualTo( "red" ); + assertThat( carDto.getId() ).isEqualTo( 42L ); + } + + @Test + public void explicitInheritedMappingIsAppliedInReverse() { + CarEntity carEntity = new CarEntity(); + carEntity.setColor( "red" ); + carEntity.setPrimaryKey( 42L ); + + CarDto carDto = CarMapperWithExplicitInheritance.INSTANCE.toCarDto( carEntity ); + + assertThat( carDto.getColour() ).isEqualTo( "red" ); + assertThat( carDto.getId() ).isEqualTo( 42L ); + } + + @Test + public void explicitInheritedMappingWithTwoLevelsIsOverriddenAtMethodLevel() { + CarDto carDto = newTestDto(); + + CarEntity carEntity = CarMapperWithExplicitInheritance.INSTANCE.toCarEntityWithFixedAuditTrail( carDto ); + + assertThat( carEntity.getColor() ).isEqualTo( "red" ); + assertThat( carEntity.getPrimaryKey() ).isEqualTo( 42L ); + assertThat( carEntity.getAuditTrail() ).isEqualTo( "fixed" ); + } + + @Test + public void explicitInheritedMappingIsApplied() { + CarDto carDto = newTestDto(); + + CarEntity carEntity = CarMapperWithExplicitInheritance.INSTANCE.toCarEntity( carDto ); + + assertEntity( carEntity ); + } + + @Test + @WithClasses( { + DriverDto.class, + CarWithDriverEntity.class, + CarWithDriverMapperWithAutoInheritance.class, + AutoInheritedDriverConfig.class } ) + public void autoInheritedFromMultipleSources() { + CarDto carDto = newTestDto(); + DriverDto driverDto = new DriverDto(); + driverDto.setName( "Malcroft" ); + + CarWithDriverEntity carWithDriverEntity = + CarWithDriverMapperWithAutoInheritance.INSTANCE.toCarWithDriverEntity( carDto, driverDto ); + + assertEntity( carWithDriverEntity ); + assertThat( carWithDriverEntity.getDriverName() ).isEqualTo( "Malcroft" ); + } + + @Test + @WithClasses( { Erroneous1Mapper.class, Erroneous1Config.class } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( type = Erroneous1Mapper.class, + kind = Kind.ERROR, + line = 37, + messageRegExp = "More than one configuration prototype method is applicable. Use @InheritConfiguration" + + " to select one of them explicitly:" + + " .*BaseVehicleEntity baseDtoToEntity\\(.*BaseVehicleDto dto\\)," + + " .*BaseVehicleEntity anythingToEntity\\(java.lang.Object anyting\\)\\." ), + @Diagnostic( type = Erroneous1Mapper.class, + kind = Kind.WARNING, + line = 37, + messageRegExp = "Unmapped target properties: \"auditTrail, primaryKey\"\\." ), + @Diagnostic( type = Erroneous1Mapper.class, + kind = Kind.ERROR, + line = 43, + messageRegExp = "More than one configuration prototype method is applicable. Use @InheritConfiguration" + + " to select one of them explicitly:" + + " .*BaseVehicleEntity baseDtoToEntity\\(.*BaseVehicleDto dto\\)," + + " .*BaseVehicleEntity anythingToEntity\\(java.lang.Object anyting\\)\\." ), + @Diagnostic( type = Erroneous1Mapper.class, + kind = Kind.WARNING, + line = 43, + messageRegExp = "Unmapped target property: \"primaryKey\"\\." ) + } + ) + public void erroneous1MultiplePrototypeMethodsMatch() { + + } + + @Test + @WithClasses( { Erroneous2Mapper.class } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( type = Erroneous2Mapper.class, + kind = Kind.ERROR, + line = 39, + messageRegExp = "Cycle detected while evaluating inherited configurations. Inheritance path:" + + " .*CarEntity toCarEntity1\\(.*CarDto carDto\\)" + + " -> .*CarEntity toCarEntity2\\(.*CarDto carDto\\)" + + " -> void toCarEntity3\\(.*CarDto carDto, @MappingTarget .*CarEntity entity\\)" + + " -> .*CarEntity toCarEntity1\\(.*CarDto carDto\\)" ) + } + ) + public void erroneous2InheritanceCycle() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java b/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java index b986aa17c..23824c1a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/prism/EnumPrismsTest.java @@ -23,11 +23,13 @@ import java.util.List; import org.junit.Test; import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.MappingInheritanceStrategy; +import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ap.prism.CollectionMappingStrategyPrism; +import org.mapstruct.ap.prism.MappingInheritanceStrategyPrism; +import org.mapstruct.ap.prism.NullValueMappingStrategyPrism; import static org.fest.assertions.Assertions.assertThat; -import org.mapstruct.NullValueMappingStrategy; -import org.mapstruct.ap.prism.NullValueMappingStrategyPrism; /** * Test for manually created prisms on enumeration types @@ -47,6 +49,12 @@ public class EnumPrismsTest { namesOf( NullValueMappingStrategyPrism.values() ) ); } + @Test + public void mapMappingInheritanceStrategyPrismIsCorrect() { + assertThat( namesOf( MappingInheritanceStrategy.values() ) ).isEqualTo( + namesOf( MappingInheritanceStrategyPrism.values() ) ); + } + private static List namesOf(Enum[] values) { List names = new ArrayList( values.length ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/reverse/InheritInverseConfigurationTest.java b/processor/src/test/java/org/mapstruct/ap/test/reverse/InheritInverseConfigurationTest.java index 5be18c1f4..1c51741ef 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/reverse/InheritInverseConfigurationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/reverse/InheritInverseConfigurationTest.java @@ -108,8 +108,8 @@ public class InheritInverseConfigurationTest { @Diagnostic(type = SourceTargetMapperAmbiguous3.class, kind = Kind.ERROR, line = 50, - messageRegExp = "Given name \"forward\" matches several candidate methods: .*forward.*\\(\\), " - + ".*forward.*\\(\\)"), + messageRegExp = "Given name \"forward\" matches several candidate methods: .*forward\\(.*\\), " + + ".*forward\\(.*\\)"), @Diagnostic(type = SourceTargetMapperAmbiguous3.class, kind = Kind.WARNING, line = 55, @@ -119,46 +119,6 @@ public class InheritInverseConfigurationTest { public void shouldRaiseAmbiguousReverseMethodErrorDuplicatedName() { } - @Test - @WithClasses({ SourceTargetMapperErroneouslyAnnotated1.class }) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = SourceTargetMapperErroneouslyAnnotated1.class, - kind = Kind.ERROR, - line = 50, - messageRegExp = "Resolved inverse mapping method reverse\\(\\) should not carry the " - + "@InheritInverseConfiguration annotation itself.") - } - ) - public void shouldUseWronglyAnnotatedError1() { - } - - @Test - @WithClasses({ SourceTargetMapperErroneouslyAnnotated2.class }) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = SourceTargetMapperErroneouslyAnnotated2.class, - kind = Kind.WARNING, - line = 45, - messageRegExp = "Unmapped target properties: \"stringPropX, integerPropX, " - + "someConstantDownstream, propertyToIgnoreDownstream\""), - @Diagnostic(type = SourceTargetMapperErroneouslyAnnotated2.class, - kind = Kind.ERROR, - line = 47, - messageRegExp = "Resolved inverse mapping method forward2\\(\\) should not carry " - + "the @InheritConfiguration annotation"), - @Diagnostic(type = SourceTargetMapperErroneouslyAnnotated2.class, - kind = Kind.WARNING, - line = 48, - messageRegExp = "Unmapped target properties: \"stringPropY, integerPropY, " - + "propertyNotToIgnoreUpstream\"") - } - ) - public void shouldUseWronglyAnnotatedError2() { - } - @Test @WithClasses({ SourceTargetMapperNonMatchingName.class }) @ExpectedCompilationOutcome( diff --git a/processor/src/test/java/org/mapstruct/ap/test/reverse/SourceTargetMapperErroneouslyAnnotated1.java b/processor/src/test/java/org/mapstruct/ap/test/reverse/SourceTargetMapperErroneouslyAnnotated1.java deleted file mode 100644 index 51b54c6d7..000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/reverse/SourceTargetMapperErroneouslyAnnotated1.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2012-2015 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.reverse; - -import org.mapstruct.InheritInverseConfiguration; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -/** - * @author Sjaak Derksen - */ -@Mapper -public interface SourceTargetMapperErroneouslyAnnotated1 { - - SourceTargetMapperErroneouslyAnnotated1 INSTANCE = - Mappers.getMapper( SourceTargetMapperErroneouslyAnnotated1.class ); - - @Mappings({ - @Mapping(source = "stringPropX", target = "stringPropY"), - @Mapping(source = "integerPropX", target = "integerPropY"), - @Mapping(source = "propertyToIgnoreDownstream", target = "propertyNotToIgnoreUpstream") - }) - Target forward(Source source); - - @InheritInverseConfiguration(name = "forward") - @Mappings({ - @Mapping(target = "someConstantDownstream", constant = "test"), - @Mapping(target = "propertyToIgnoreDownstream", ignore = true) - }) - Source reverse(Target target); - - @InheritInverseConfiguration(name = "reverse") - @Mappings({ - @Mapping(source = "stringPropX", target = "stringPropY"), - @Mapping(source = "integerPropX", target = "integerPropY"), - @Mapping(source = "propertyToIgnoreDownstream", target = "propertyNotToIgnoreUpstream") - }) - Target forward2(Source source); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/template/InheritConfigurationTest.java b/processor/src/test/java/org/mapstruct/ap/test/template/InheritConfigurationTest.java index dc67afac5..883d66115 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/template/InheritConfigurationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/template/InheritConfigurationTest.java @@ -182,36 +182,6 @@ public class InheritConfigurationTest { public void shouldRaiseAmbiguousReverseMethodErrorDuplicatedName() { } - @Test - @WithClasses({ SourceTargetMapperErroneouslyAnnotated1.class }) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = SourceTargetMapperErroneouslyAnnotated1.class, - kind = Kind.ERROR, - line = 49, - messageRegExp = "Resolved mapping method forwardCreate1\\(\\) should not carry the " - + "@InheritConfiguration annotation itself.") - } - ) - public void shouldUseWronglyAnnotatedError1() { - } - - @Test - @WithClasses({ SourceTargetMapperErroneouslyAnnotated2.class }) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = SourceTargetMapperErroneouslyAnnotated2.class, - kind = Kind.ERROR, - line = 51, - messageRegExp = "Resolved mapping method forwardCreate\\(\\) should not carry the " - + "@InheritInverseConfiguration annotation.") - } - ) - public void shouldUseWronglyAnnotatedError2() { - } - @Test @WithClasses({ SourceTargetMapperNonMatchingName.class }) @ExpectedCompilationOutcome( diff --git a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperErroneouslyAnnotated1.java b/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperErroneouslyAnnotated1.java deleted file mode 100644 index 23499504b..000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperErroneouslyAnnotated1.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2012-2015 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.template; - -import org.mapstruct.InheritConfiguration; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -/** - * @author Sjaak Derksen - */ -@Mapper -public interface SourceTargetMapperErroneouslyAnnotated1 { - - SourceTargetMapperErroneouslyAnnotated1 INSTANCE = - Mappers.getMapper( SourceTargetMapperErroneouslyAnnotated1.class ); - - @Mappings({ - @Mapping(target = "stringPropY", source = "stringPropX"), - @Mapping(target = "integerPropY", source = "integerPropX"), - @Mapping(target = "nestedResultProp", source = "nestedSourceProp.nested"), - @Mapping(target = "constantProp", constant = "constant"), - @Mapping(target = "expressionProp", expression = "java(\"expression\")") - }) - Target forwardCreate(Source source); - - @InheritConfiguration( name = "forwardCreate" ) - Target forwardCreate1(Source source); - - @InheritConfiguration( name = "forwardCreate1" ) - void forwardUpdate(Source source, @MappingTarget Target target); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSeveralArgs.java b/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSeveralArgs.java index d5dfa7d05..03e319b8e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSeveralArgs.java +++ b/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSeveralArgs.java @@ -34,11 +34,11 @@ public interface SourceTargetMapperSeveralArgs { SourceTargetMapperSeveralArgs INSTANCE = Mappers.getMapper( SourceTargetMapperSeveralArgs.class ); @Mappings({ - @Mapping(target = "stringPropY", source = "source.stringPropX"), - @Mapping(target = "integerPropY", source = "source.integerPropX"), - @Mapping(target = "nestedResultProp", source = "source.nestedSourceProp.nested") + @Mapping( target = "stringPropY", source = "s1.stringPropX" ), + @Mapping( target = "integerPropY", source = "s1.integerPropX" ), + @Mapping( target = "nestedResultProp", source = "s1.nestedSourceProp.nested" ) }) - Target forwardCreate(Source source, String constantProp, String expressionProp); + Target forwardCreate(Source s1, String constantProp, String expressionProp); @InheritConfiguration void forwardUpdate(Source source, String constantProp, String expressionProp, @MappingTarget Target target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSingle.java b/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSingle.java index a6d70870b..8c892c8fa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSingle.java +++ b/processor/src/test/java/org/mapstruct/ap/test/template/SourceTargetMapperSingle.java @@ -40,7 +40,7 @@ public interface SourceTargetMapperSingle { @Mapping(target = "constantProp", constant = "constant"), @Mapping(target = "expressionProp", expression = "java(\"expression\")"), }) - Target forwardCreate(Source source); + Target forwardCreate(Source s1); @InheritConfiguration