#14 Adapt javadoc and order of method-usage within a type hierarchy

This commit is contained in:
Andreas Gudian 2015-04-13 21:37:57 +02:00
parent 619f8023d5
commit ccedca8890
8 changed files with 198 additions and 46 deletions

View File

@ -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.
* <p>
* 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.
* <p>
* If the method has parameters, the method invocation is only generated if all parameters can be <em>assigned</em> by
* the source or target parameters of the mapping method:
* <ul>
* <li>A parameter annotated with {@code @}{@link MappingTarget} is populated with the target instance of the mapping.
* </li>
* <li>A parameter annotated with {@code @}{@link TargetType} is populated with the target type of the mapping.</li>
* <li>Any other parameter is populated with a source parameter of the mapping, whereas each source parameter is used
* once at most.</li>
* </ul>
* <p>
* All <em>after-mapping</em> methods that can be applied to a mapping method will be used. Their order is determined by
* their location of definition:
* <ul>
* <li>The order of methods within one type can not be guaranteed, as it depends on the compiler and the processing
* environment implementation.</li>
* <li>Methods declared in one type are used after methods declared in their super-type.</li>
* <li>Methods implemented in the mapper itself are used before methods from types referenced in {@link Mapper#uses()}.
* </li>
* <li>Types referenced in {@link Mapper#uses()} are searched for <em>after-mapping</em> methods in the order specified
* in the annotation.</li>
* </ul>
* <p>
* Example:
*
* <pre>
* <code>
* &#64;AfterMapping
* public void calledWithoutArgs() {
* // ...
* }
*
* &#64;AfterMapping
* public void calledWithSourceAndTargetType(SourceEntity anySource, &#64;TargetType Class&lt;?&gt; targetType) {
* // ...
* }
*
* &#64;AfterMapping
* public void calledWithSourceAndTarget(Object anySource, &#64;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;
* }
* </code>
* </pre>
*
* @author Andreas Gudian
* @see BeforeMapping
*/
@Experimental
@Target(ElementType.METHOD)

View File

@ -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.
* <p>
* 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.
* <p>
* If the method has parameters, the method invocation is only generated if all parameters can be <em>assigned</em> by
* the source or target parameters of the mapping method:
* <ul>
* <li>A parameter annotated with {@code @}{@link MappingTarget} is populated with the target instance of the mapping.
* </li>
* <li>A parameter annotated with {@code @}{@link TargetType} is populated with the target type of the mapping.</li>
* <li>Any other parameter is populated with a source parameter of the mapping, whereas each source parameter is used
* once at most.</li>
* </ul>
* If a <em>before-mapping</em> 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.
* <p>
* All <em>before-mapping</em> methods that can be applied to a mapping method will be used. Their order is determined
* by their location of definition:
* <ul>
* <li>The order of methods within one type can not be guaranteed, as it depends on the compiler and the processing
* environment implementation.</li>
* <li>Methods declared in one type are used after methods declared in their super-type.</li>
* <li>Methods implemented in the mapper itself are used before methods from types referenced in {@link Mapper#uses()}.
* </li>
* <li>Types referenced in {@link Mapper#uses()} are searched for <em>after-mapping</em> methods in the order specified
* in the annotation.</li>
* </ul>
* <p>
* Example:
*
* <pre>
* <code>
* &#64;BeforeMapping
* public void calledWithoutArgs() {
* // ...
* }
*
* &#64;BeforeMapping
* public void calledWithSourceAndTargetType(SourceEntity anySource, &#64;TargetType Class&lt;?&gt; targetType) {
* // ...
* }
*
* &#64;BeforeMapping
* public void calledWithSourceAndTarget(Object anySource, &#64;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;
* }
* </code>
* </pre>
*
* @author Andreas Gudian
* @see AfterMapping
*/
@Experimental
@Target(ElementType.METHOD)

View File

@ -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.
* <p>
* A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method.
*
* @return the qualifiers

View File

@ -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<TypeVariable, TypeMirror> 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<TypeVariable, TypeMirror> 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
private boolean isJavaLangObject(TypeMirror type) {
return type.getKind() == TypeKind.DECLARED
&& ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName().contentEquals(
Object.class.getName() ) );
}
private static <E> boolean hasNonNullElements(List<E> elements) {
if ( elements != null ) {
for ( E e : elements ) {
if ( e != null ) {
return true;
}
}
}
return false;
Object.class.getName() );
}
private enum Assignability {

View File

@ -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() );
}
}

View File

@ -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<Void, Lis
}
private boolean isValidLifecycleCallbackMethod(ExecutableElement method, Type returnType) {
return isVoid( returnType ) && SourceMethod.isLifecycleCallbackMethod( method );
return isVoid( returnType ) && Executables.isLifecycleCallbackMethod( method );
}
private boolean isValidReferencedMethod(List<Parameter> parameters) {

View File

@ -85,4 +85,15 @@ public class Collections {
return result;
}
public static <E> boolean hasNonNullElements(Iterable<E> elements) {
if ( elements != null ) {
for ( E e : elements ) {
if ( e != null ) {
return true;
}
}
}
return false;
}
}

View File

@ -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;
}
}