From ccedca88905023fc362bd3398badf11b608c6d1c Mon Sep 17 00:00:00 2001 From: Andreas Gudian Date: Mon, 13 Apr 2015 21:37:57 +0200 Subject: [PATCH] #14 Adapt javadoc and order of method-usage within a type hierarchy --- .../main/java/org/mapstruct/AfterMapping.java | 71 +++++++++++++++++- .../java/org/mapstruct/BeforeMapping.java | 75 ++++++++++++++++++- .../main/java/org/mapstruct/MapMapping.java | 7 +- .../ap/model/source/MethodMatcher.java | 26 ++----- .../ap/model/source/SourceMethod.java | 21 +----- .../processor/MethodRetrievalProcessor.java | 3 +- .../org/mapstruct/ap/util/Collections.java | 11 +++ .../org/mapstruct/ap/util/Executables.java | 30 +++++++- 8 files changed, 198 insertions(+), 46 deletions(-) diff --git a/core-common/src/main/java/org/mapstruct/AfterMapping.java b/core-common/src/main/java/org/mapstruct/AfterMapping.java index d43beb8d6..6a62ef64f 100644 --- a/core-common/src/main/java/org/mapstruct/AfterMapping.java +++ b/core-common/src/main/java/org/mapstruct/AfterMapping.java @@ -26,12 +26,77 @@ import java.lang.annotation.Target; import org.mapstruct.util.Experimental; /** - * Marks a method to be invoked at the end of a generated the mapping method. + * Marks a method to be invoked at the end of a generated mapping method, right before the last {@code return} statement + * of the mapping method. The method can be implemented in an abstract mapper class or be declared in a type (class or + * interface) referenced in {@link Mapper#uses()} in order to be used in a mapping method. *

- * If the method has parameters, the method invocation is only generated if all parameters can be assigned by the source - * or target parameters of the mapping method. + * Only methods with return type {@code void} may be annotated with this annotation. + *

+ * If the method has parameters, the method invocation is only generated if all parameters can be assigned by + * the source or target parameters of the mapping method: + *

+ *

+ * All after-mapping methods that can be applied to a mapping method will be used. Their order is determined by + * their location of definition: + *

+ *

+ * Example: + * + *

+ * 
+ * @AfterMapping
+ * public void calledWithoutArgs() {
+ *     // ...
+ * }
+ *
+ * @AfterMapping
+ * public void calledWithSourceAndTargetType(SourceEntity anySource, @TargetType Class<?> targetType) {
+ *     // ...
+ * }
+ *
+ * @AfterMapping
+ * public void calledWithSourceAndTarget(Object anySource, @MappingTarget TargetDto target) {
+ *     // ...
+ * }
+ *
+ * public abstract TargetDto toTargetDto(SourceEntity source);
+ *
+ * // generates:
+ *
+ * public TargetDto toTargetDto(SourceEntity source) {
+ *     if ( source == null ) {
+ *         return null;
+ *     }
+ *
+ *     TargetDto targetDto = new TargetDto();
+ *
+ *     // actual mapping code
+ *
+ *     calledWithoutArgs();
+ *     calledWithSourceAndTargetType( source, TargetDto.class );
+ *     calledWithSourceAndTarget( source, targetDto );
+ *
+ *     return targetDto;
+ * }
+ * 
+ * 
* * @author Andreas Gudian + * @see BeforeMapping */ @Experimental @Target(ElementType.METHOD) diff --git a/core-common/src/main/java/org/mapstruct/BeforeMapping.java b/core-common/src/main/java/org/mapstruct/BeforeMapping.java index 88a14d992..23995323b 100644 --- a/core-common/src/main/java/org/mapstruct/BeforeMapping.java +++ b/core-common/src/main/java/org/mapstruct/BeforeMapping.java @@ -26,12 +26,81 @@ import java.lang.annotation.Target; import org.mapstruct.util.Experimental; /** - * Marks a method to be invoked at the beginning of a generated mapping method. + * Marks a method to be invoked at the beginning of a generated mapping method. The method can be implemented in an + * abstract mapper class or be declared in a type (class or interface) referenced in {@link Mapper#uses()} in order to + * be used in a mapping method. *

- * If the method has parameters, the method invocation is only generated if all parameters can be assigned by the source - * or target parameters of the mapping method. + * Only methods with return type {@code void} may be annotated with this annotation. + *

+ * If the method has parameters, the method invocation is only generated if all parameters can be assigned by + * the source or target parameters of the mapping method: + *

+ * If a before-mapping method does not contain a {@code @}{@link MappingTarget} parameter, it is invoked + * directly at the beginning of the applicable mapping method. If it contains a {@code @}{@link MappingTarget} + * parameter, the method is invoked after the target parameter has been initialized in the mapping method. + *

+ * All before-mapping methods that can be applied to a mapping method will be used. Their order is determined + * by their location of definition: + *

+ *

+ * Example: + * + *

+ * 
+ * @BeforeMapping
+ * public void calledWithoutArgs() {
+ *     // ...
+ * }
+ *
+ * @BeforeMapping
+ * public void calledWithSourceAndTargetType(SourceEntity anySource, @TargetType Class<?> targetType) {
+ *     // ...
+ * }
+ *
+ * @BeforeMapping
+ * public void calledWithSourceAndTarget(Object anySource, @MappingTarget TargetDto target) {
+ *     // ...
+ * }
+ *
+ * public abstract TargetDto toTargetDto(SourceEntity source);
+ *
+ * // generates:
+ *
+ * public TargetDto toTargetDto(SourceEntity source) {
+ *     calledWithoutArgs();
+ *     calledWithSourceAndTargetType( source, TargetDto.class );
+ *
+ *     if ( source == null ) {
+ *         return null;
+ *     }
+ *
+ *     TargetDto targetDto = new TargetDto();
+ *
+ *     calledWithSourceAndTarget( source, targetDto );
+ *
+ *     // actual mapping code
+ *
+ *     return targetDto;
+ * }
+ * 
+ * 
* * @author Andreas Gudian + * @see AfterMapping */ @Experimental @Target(ElementType.METHOD) diff --git a/core-common/src/main/java/org/mapstruct/MapMapping.java b/core-common/src/main/java/org/mapstruct/MapMapping.java index e754ee0e8..044c2e818 100644 --- a/core-common/src/main/java/org/mapstruct/MapMapping.java +++ b/core-common/src/main/java/org/mapstruct/MapMapping.java @@ -67,9 +67,10 @@ public @interface MapMapping { /** - * A value qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case - * multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found' error. - * + * A value qualifier can be specified to aid the selection process of a suitable mapper for the values in the map. + * This is useful in case multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping + * methods found' error. + *

* A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. * * @return the qualifiers diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java index 13c58c5fd..b90b9af9b 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java @@ -38,6 +38,7 @@ import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.TypeFactory; +import static org.mapstruct.ap.util.Collections.hasNonNullElements; import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.isSubType; /** @@ -146,7 +147,7 @@ public class MethodMatcher { Type candidateSourceType, Map genericTypesMap) { - if ( isNotObjectClass( candidateSourceType.getTypeMirror() ) ) { + if ( !isJavaLangObject( candidateSourceType.getTypeMirror() ) ) { TypeMatcher parameterMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_FROM, genericTypesMap ); if ( !parameterMatcher.visit( candidateSourceType.getTypeMirror(), sourceType.getTypeMirror() ) ) { if ( sourceType.isPrimitive() ) { @@ -171,7 +172,7 @@ public class MethodMatcher { Map genericTypesMap) { - if ( isNotObjectClass( candidateResultType.getTypeMirror() ) && !candidateResultType.isVoid() ) { + if ( !isJavaLangObject( candidateResultType.getTypeMirror() ) && !candidateResultType.isVoid() ) { TypeMatcher returnTypeMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); if ( !returnTypeMatcher.visit( candidateResultType.getTypeMirror(), resultType.getTypeMirror() ) ) { @@ -205,23 +206,12 @@ public class MethodMatcher { /** * @param type the type - * @return {@code true}, if the type does NOT represent java.lang.Object + * @return {@code true}, if the type represents java.lang.Object */ - private boolean isNotObjectClass(TypeMirror type) { - return !( type.getKind() == TypeKind.DECLARED - && ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName().contentEquals( - Object.class.getName() ) ); - } - - private static boolean hasNonNullElements(List elements) { - if ( elements != null ) { - for ( E e : elements ) { - if ( e != null ) { - return true; - } - } - } - return false; + private boolean isJavaLangObject(TypeMirror type) { + return type.getKind() == TypeKind.DECLARED + && ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName().contentEquals( + Object.class.getName() ); } private enum Assignability { 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 9eb18c603..effa3a248 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 @@ -34,8 +34,7 @@ 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.prism.AfterMappingPrism; -import org.mapstruct.ap.prism.BeforeMappingPrism; +import org.mapstruct.ap.util.Executables; import org.mapstruct.ap.util.FormattingMessager; import org.mapstruct.ap.util.MapperConfiguration; import org.mapstruct.ap.util.Strings; @@ -520,26 +519,14 @@ public class SourceMethod implements Method { @Override public boolean isLifecycleCallbackMethod() { - return isBeforeMappingMethod() || isAfterMappingMethod(); + return Executables.isLifecycleCallbackMethod( getExecutable() ); } public boolean isAfterMappingMethod() { - return isAfterMappingMethod( getExecutable() ); + return Executables.isAfterMappingMethod( getExecutable() ); } public boolean isBeforeMappingMethod() { - return isBeforeMappingMethod( getExecutable() ); - } - - public static boolean isLifecycleCallbackMethod(ExecutableElement executableElement) { - return isBeforeMappingMethod( executableElement ) || isAfterMappingMethod( executableElement ); - } - - private static boolean isAfterMappingMethod(ExecutableElement executableElement) { - return AfterMappingPrism.getInstanceOn( executableElement ) != null; - } - - private static boolean isBeforeMappingMethod(ExecutableElement executableElement) { - return BeforeMappingPrism.getInstanceOn( executableElement ) != null; + return Executables.isBeforeMappingMethod( getExecutable() ); } } 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 e8c6f8343..f8825a650 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -48,6 +48,7 @@ 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.Executables; import org.mapstruct.ap.util.FormattingMessager; import org.mapstruct.ap.util.MapperConfiguration; import org.mapstruct.ap.util.Message; @@ -272,7 +273,7 @@ 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 e41e4715b..642608bfd 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Collections.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Collections.java @@ -85,4 +85,15 @@ public class Collections { return result; } + + public static boolean hasNonNullElements(Iterable elements) { + if ( elements != null ) { + for ( E e : elements ) { + if ( e != null ) { + return true; + } + } + } + return false; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/util/Executables.java b/processor/src/main/java/org/mapstruct/ap/util/Executables.java index c5426eb1a..a60b3b170 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Executables.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Executables.java @@ -34,6 +34,9 @@ import org.mapstruct.ap.services.Services; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.MethodType; +import org.mapstruct.ap.prism.AfterMappingPrism; +import org.mapstruct.ap.prism.BeforeMappingPrism; + import static javax.lang.model.util.ElementFilter.methodsIn; import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; @@ -163,7 +166,7 @@ public class Executables { } } - alreadyCollected.addAll( safeToAdd ); + alreadyCollected.addAll( 0, safeToAdd ); } /** @@ -209,4 +212,29 @@ public class Executables { return element.getSuperclass().getKind() == TypeKind.DECLARED && asTypeElement( element.getSuperclass() ).getSuperclass().getKind() == TypeKind.DECLARED; } + + /** + * @param executableElement the element to check + * @return {@code true}, if the executable element is a method annotated with {@code @BeforeMapping} or + * {@code @AfterMapping} + */ + public static boolean isLifecycleCallbackMethod(ExecutableElement executableElement) { + return isBeforeMappingMethod( executableElement ) || isAfterMappingMethod( executableElement ); + } + + /** + * @param executableElement the element to check + * @return {@code true}, if the executable element is a method annotated with {@code @AfterMapping} + */ + public static boolean isAfterMappingMethod(ExecutableElement executableElement) { + return AfterMappingPrism.getInstanceOn( executableElement ) != null; + } + + /** + * @param executableElement the element to check + * @return {@code true}, if the executable element is a method annotated with {@code @BeforeMapping} + */ + public static boolean isBeforeMappingMethod(ExecutableElement executableElement) { + return BeforeMappingPrism.getInstanceOn( executableElement ) != null; + } }