mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#32 Establishing processing chain for model creation and transformation
This commit is contained in:
parent
9a4e51801f
commit
a565bed0c7
@ -18,9 +18,11 @@
|
||||
*/
|
||||
package org.mapstruct.ap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.Processor;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.annotation.processing.SupportedOptions;
|
||||
@ -28,18 +30,45 @@ import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.ElementKindVisitor6;
|
||||
|
||||
import net.java.dev.hickory.prism.GeneratePrism;
|
||||
import net.java.dev.hickory.prism.GeneratePrisms;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.ap.model.Mapper;
|
||||
import org.mapstruct.ap.model.Options;
|
||||
import org.mapstruct.ap.model.ReportingPolicy;
|
||||
import org.mapstruct.ap.processor.DefaultModelElementProcessorContext;
|
||||
import org.mapstruct.ap.processor.MapperCreationProcessor;
|
||||
import org.mapstruct.ap.processor.MapperRenderingProcessor;
|
||||
import org.mapstruct.ap.processor.MethodRetrievalProcessor;
|
||||
import org.mapstruct.ap.processor.ModelElementProcessor;
|
||||
import org.mapstruct.ap.processor.ModelElementProcessor.ProcessorContext;
|
||||
|
||||
/**
|
||||
* A {@link Processor} which generates the implementations for mapper interfaces
|
||||
* (interfaces annotated with {@code @Mapper}.
|
||||
* </p>
|
||||
* Implementation notes:
|
||||
* </p>
|
||||
* The generation happens by incrementally building up a model representation of
|
||||
* each mapper to be generated (a {@link Mapper} object), which is then written
|
||||
* into the resulting Java source file using the FreeMarker template engine.
|
||||
* </p>
|
||||
* The model instantiation and processing happens in several phases/passes by applying
|
||||
* a sequence of {@link ModelElementProcessor}s.
|
||||
* </p>
|
||||
* For reading annotation attributes, prisms as generated with help of the <a
|
||||
* href="https://java.net/projects/hickory">Hickory</a> tool are used. These
|
||||
* prisms allow a comfortable access to annotations and their attributes without
|
||||
* depending on their class objects.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
@SupportedAnnotationTypes("org.mapstruct.Mapper")
|
||||
@GeneratePrisms({
|
||||
@GeneratePrism(value = Mapper.class, publicAccess = true),
|
||||
@GeneratePrism(value = org.mapstruct.Mapper.class, publicAccess = true),
|
||||
@GeneratePrism(value = Mapping.class, publicAccess = true),
|
||||
@GeneratePrism(value = Mappings.class, publicAccess = true)
|
||||
})
|
||||
@ -63,32 +92,6 @@ public class MappingProcessor extends AbstractProcessor {
|
||||
options = createOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(
|
||||
final Set<? extends TypeElement> annotations,
|
||||
final RoundEnvironment roundEnvironment) {
|
||||
|
||||
for ( TypeElement oneAnnotation : annotations ) {
|
||||
|
||||
//Indicates that the annotation's type isn't on the class path of the compiled
|
||||
//project. Let the compiler deal with that and print an appropriate error.
|
||||
if ( oneAnnotation.getKind() != ElementKind.ANNOTATION_TYPE ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( Element oneAnnotatedElement : roundEnvironment.getElementsAnnotatedWith( oneAnnotation ) ) {
|
||||
oneAnnotatedElement.accept( new MapperGenerationVisitor( processingEnv, options ), null );
|
||||
}
|
||||
}
|
||||
|
||||
return ANNOTATIONS_CLAIMED_EXCLUSIVELY;
|
||||
}
|
||||
|
||||
private Options createOptions() {
|
||||
String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY );
|
||||
|
||||
@ -97,4 +100,65 @@ public class MappingProcessor extends AbstractProcessor {
|
||||
unmappedTargetPolicy != null ? ReportingPolicy.valueOf( unmappedTargetPolicy ) : null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion() {
|
||||
return SourceVersion.latestSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) {
|
||||
ProcessorContext context = new DefaultModelElementProcessorContext( processingEnv, options );
|
||||
|
||||
for ( TypeElement annotation : annotations ) {
|
||||
|
||||
//Indicates that the annotation's type isn't on the class path of the compiled
|
||||
//project. Let the compiler deal with that and print an appropriate error.
|
||||
if ( annotation.getKind() != ElementKind.ANNOTATION_TYPE ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( Element mapperElement : roundEnvironment.getElementsAnnotatedWith( annotation ) ) {
|
||||
TypeElement mapperTypeElement = asTypeElement( mapperElement );
|
||||
processMapperTypeElement( context, mapperTypeElement );
|
||||
}
|
||||
}
|
||||
|
||||
return ANNOTATIONS_CLAIMED_EXCLUSIVELY;
|
||||
}
|
||||
|
||||
private void processMapperTypeElement(ProcessorContext context, TypeElement mapperTypeElement) {
|
||||
Object mapper = mapperTypeElement;
|
||||
for ( ModelElementProcessor<?, ?> processor : getProcessors() ) {
|
||||
mapper = process( context, processor, mapperTypeElement, mapper );
|
||||
}
|
||||
}
|
||||
|
||||
private <P, R> R process(ProcessorContext context, ModelElementProcessor<P, R> processor,
|
||||
TypeElement mapperTypeElement, Object modelElement) {
|
||||
@SuppressWarnings("unchecked")
|
||||
P sourceElement = (P) modelElement;
|
||||
return processor.process( context, mapperTypeElement, sourceElement );
|
||||
}
|
||||
|
||||
//TODO Retrieve via service loader
|
||||
private Iterable<ModelElementProcessor<?, ?>> getProcessors() {
|
||||
return Arrays.<ModelElementProcessor<?, ?>>asList(
|
||||
new MethodRetrievalProcessor(),
|
||||
new MapperCreationProcessor(),
|
||||
new MapperRenderingProcessor()
|
||||
);
|
||||
}
|
||||
|
||||
private TypeElement asTypeElement(Element element) {
|
||||
return element.accept(
|
||||
new ElementKindVisitor6<TypeElement, Void>() {
|
||||
@Override
|
||||
public TypeElement visitTypeAsInterface(TypeElement e, Void p) {
|
||||
return e;
|
||||
}
|
||||
|
||||
}, null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +28,12 @@ import java.util.Set;
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
public class SimpleMappingMethod extends MappingMethod {
|
||||
public class BeanMappingMethod extends MappingMethod {
|
||||
|
||||
private final List<PropertyMapping> propertyMappings;
|
||||
|
||||
public SimpleMappingMethod(String name, String parameterName, Type sourceType, Type targetType,
|
||||
List<PropertyMapping> propertyMappings) {
|
||||
public BeanMappingMethod(String name, String parameterName, Type sourceType, Type targetType,
|
||||
List<PropertyMapping> propertyMappings) {
|
||||
super( name, parameterName, sourceType, targetType );
|
||||
this.propertyMappings = propertyMappings;
|
||||
}
|
@ -33,10 +33,11 @@ public class Mapper extends AbstractModelElement {
|
||||
private final List<Type> usedMapperTypes;
|
||||
private final Options options;
|
||||
private final SortedSet<Type> importedTypes;
|
||||
private final boolean isErroneous;
|
||||
|
||||
public Mapper(String packageName, String interfaceName,
|
||||
String implementationName, List<MappingMethod> mappingMethods, List<Type> usedMapperTypes,
|
||||
Options options) {
|
||||
Options options, boolean isErroneous) {
|
||||
this.packageName = packageName;
|
||||
this.interfaceName = interfaceName;
|
||||
this.implementationName = implementationName;
|
||||
@ -44,6 +45,7 @@ public class Mapper extends AbstractModelElement {
|
||||
this.usedMapperTypes = usedMapperTypes;
|
||||
this.options = options;
|
||||
this.importedTypes = determineImportedTypes();
|
||||
this.isErroneous = isErroneous;
|
||||
}
|
||||
|
||||
private SortedSet<Type> determineImportedTypes() {
|
||||
@ -122,4 +124,8 @@ public class Mapper extends AbstractModelElement {
|
||||
public SortedSet<Type> getImportedTypes() {
|
||||
return importedTypes;
|
||||
}
|
||||
|
||||
public boolean isErroneous() {
|
||||
return isErroneous;
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,10 @@ public class Type implements Comparable<Type> {
|
||||
this( null, name, null, false, false, false );
|
||||
}
|
||||
|
||||
public Type(String packageName, String name) {
|
||||
this( packageName, name, null, false, false, false );
|
||||
}
|
||||
|
||||
public Type(String packageName, String name, Type elementType, boolean isEnumType, boolean isCollectionType,
|
||||
boolean isIterableType) {
|
||||
this.packageName = packageName;
|
||||
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright 2012-2013 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.processor;
|
||||
|
||||
import javax.annotation.processing.Filer;
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
import org.mapstruct.ap.model.Options;
|
||||
|
||||
/**
|
||||
* Default implementation of the processor context.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
public class DefaultModelElementProcessorContext implements ModelElementProcessor.ProcessorContext {
|
||||
|
||||
private ProcessingEnvironment processingEnvironment;
|
||||
private Options options;
|
||||
|
||||
public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options) {
|
||||
this.processingEnvironment = processingEnvironment;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filer getFiler() {
|
||||
return processingEnvironment.getFiler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Types getTypeUtils() {
|
||||
return processingEnvironment.getTypeUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Elements getElementUtils() {
|
||||
return processingEnvironment.getElementUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Messager getMessager() {
|
||||
return processingEnvironment.getMessager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Options getOptions() {
|
||||
return options;
|
||||
}
|
||||
}
|
@ -16,10 +16,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.mapstruct.ap;
|
||||
package org.mapstruct.ap.processor;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -28,23 +27,20 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementVisitor;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementKindVisitor6;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import org.mapstruct.ap.MapperPrism;
|
||||
import org.mapstruct.ap.conversion.Conversion;
|
||||
import org.mapstruct.ap.conversion.Conversions;
|
||||
import org.mapstruct.ap.model.BeanMappingMethod;
|
||||
import org.mapstruct.ap.model.IterableMappingMethod;
|
||||
import org.mapstruct.ap.model.Mapper;
|
||||
import org.mapstruct.ap.model.MappingMethod;
|
||||
@ -52,111 +48,63 @@ import org.mapstruct.ap.model.MappingMethodReference;
|
||||
import org.mapstruct.ap.model.Options;
|
||||
import org.mapstruct.ap.model.PropertyMapping;
|
||||
import org.mapstruct.ap.model.ReportingPolicy;
|
||||
import org.mapstruct.ap.model.SimpleMappingMethod;
|
||||
import org.mapstruct.ap.model.Type;
|
||||
import org.mapstruct.ap.model.source.Mapping;
|
||||
import org.mapstruct.ap.model.source.Method;
|
||||
import org.mapstruct.ap.model.source.Parameter;
|
||||
import org.mapstruct.ap.util.Executables;
|
||||
import org.mapstruct.ap.util.Filters;
|
||||
import org.mapstruct.ap.util.Strings;
|
||||
import org.mapstruct.ap.util.TypeUtil;
|
||||
import org.mapstruct.ap.writer.ModelWriter;
|
||||
|
||||
import static javax.lang.model.util.ElementFilter.methodsIn;
|
||||
|
||||
/**
|
||||
* An {@link ElementVisitor} which generates the implementations for mapper
|
||||
* interfaces (interfaces annotated with {@code @Mapper}.
|
||||
* </p>
|
||||
* Implementation notes:
|
||||
* </p>
|
||||
* The mapper generation happens by building up a model representation of
|
||||
* the mapper to be generated (a {@link Mapper} object), which is then written
|
||||
* into a file using the FreeMarker template engine.
|
||||
* </p>
|
||||
* The model instantiation happens in two phases/passes: The first one retrieves
|
||||
* the mapping methods of the given interfaces and their configuration (the
|
||||
* <i>source</i> model). In the second pass the individual methods are
|
||||
* aggregated into the <i>target</i> model, which contains a {@link BeanMapping}
|
||||
* each pair of source and target type which has references to forward and
|
||||
* reverse mapping methods as well as the methods for mapping the element types
|
||||
* (if it is a collection mapping) and {@link Conversion}s if applicable.
|
||||
* </p>
|
||||
* For reading annotation attributes, prisms as generated with help of the <a
|
||||
* href="https://java.net/projects/hickory">Hickory</a> tool are used. These
|
||||
* prisms allow a comfortable access to annotations and their attributes without
|
||||
* depending on their class objects.
|
||||
* A {@link ModelElementProcessor} which creates a {@link Mapper} from the given
|
||||
* list of {@link Method}s.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
public class MapperCreationProcessor implements ModelElementProcessor<List<Method>, Mapper> {
|
||||
|
||||
private static final String IMPLEMENTATION_SUFFIX = "Impl";
|
||||
|
||||
private final ProcessingEnvironment processingEnvironment;
|
||||
private final Types typeUtils;
|
||||
private final Elements elementUtils;
|
||||
private final TypeUtil typeUtil;
|
||||
private final Conversions conversions;
|
||||
private final Options options;
|
||||
private Elements elementUtils;
|
||||
private Types typeUtils;
|
||||
private Messager messager;
|
||||
private Options options;
|
||||
|
||||
private boolean mappingErroneous = false;
|
||||
private TypeUtil typeUtil;
|
||||
private Conversions conversions;
|
||||
private Executables executables;
|
||||
|
||||
public MapperGenerationVisitor(ProcessingEnvironment processingEnvironment, Options options) {
|
||||
this.processingEnvironment = processingEnvironment;
|
||||
this.typeUtils = processingEnvironment.getTypeUtils();
|
||||
this.elementUtils = processingEnvironment.getElementUtils();
|
||||
this.typeUtil = new TypeUtil( elementUtils, typeUtils );
|
||||
this.conversions = new Conversions( elementUtils, typeUtils, typeUtil );
|
||||
this.options = options;
|
||||
}
|
||||
private boolean isErroneous = false;
|
||||
|
||||
@Override
|
||||
public Void visitTypeAsInterface(TypeElement element, Void p) {
|
||||
Mapper model = retrieveModel( element );
|
||||
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<Method> sourceElement) {
|
||||
this.elementUtils = context.getElementUtils();
|
||||
this.typeUtils = context.getTypeUtils();
|
||||
this.messager = context.getMessager();
|
||||
this.options = context.getOptions();
|
||||
|
||||
if ( !mappingErroneous ) {
|
||||
String sourceFileName = element.getQualifiedName() + IMPLEMENTATION_SUFFIX;
|
||||
writeModelToSourceFile( sourceFileName, model );
|
||||
}
|
||||
this.typeUtil = new TypeUtil( context.getElementUtils(), context.getTypeUtils() );
|
||||
this.conversions = new Conversions( elementUtils, typeUtils, typeUtil );
|
||||
this.executables = new Executables( typeUtil );
|
||||
|
||||
return null;
|
||||
return getMapper( mapperTypeElement, sourceElement );
|
||||
}
|
||||
|
||||
private void writeModelToSourceFile(String fileName, Mapper model) {
|
||||
JavaFileObject sourceFile;
|
||||
try {
|
||||
sourceFile = processingEnvironment.getFiler().createSourceFile( fileName );
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
|
||||
new ModelWriter().writeModel( sourceFile, model );
|
||||
}
|
||||
|
||||
private Mapper retrieveModel(TypeElement element) {
|
||||
//1.) build up "source" model
|
||||
List<Method> methods = retrieveMethods( element, true );
|
||||
|
||||
//2.) build up aggregated "target" model
|
||||
List<MappingMethod> mappings = getMappingMethods(
|
||||
methods,
|
||||
getEffectiveUnmappedTargetPolicy( element )
|
||||
);
|
||||
public Mapper getMapper(TypeElement element, List<Method> methods) {
|
||||
List<Type> usedMapperTypes = getUsedMapperTypes( element );
|
||||
|
||||
Mapper mapper = new Mapper(
|
||||
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element );
|
||||
|
||||
return new Mapper(
|
||||
elementUtils.getPackageOf( element ).getQualifiedName().toString(),
|
||||
element.getSimpleName().toString(),
|
||||
element.getSimpleName() + IMPLEMENTATION_SUFFIX,
|
||||
mappings,
|
||||
getMappingMethods( methods, unmappedTargetPolicy ),
|
||||
usedMapperTypes,
|
||||
options
|
||||
options,
|
||||
isErroneous
|
||||
);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,6 +132,15 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
}
|
||||
}
|
||||
|
||||
private List<Type> getUsedMapperTypes(TypeElement element) {
|
||||
List<Type> usedMapperTypes = new LinkedList<Type>();
|
||||
MapperPrism mapperPrism = MapperPrism.getInstanceOn( element );
|
||||
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
|
||||
usedMapperTypes.add( typeUtil.retrieveType( usedMapper ) );
|
||||
}
|
||||
return usedMapperTypes;
|
||||
}
|
||||
|
||||
private List<MappingMethod> getMappingMethods(List<Method> methods, ReportingPolicy unmappedTargetPolicy) {
|
||||
List<MappingMethod> mappingMethods = new ArrayList<MappingMethod>();
|
||||
|
||||
@ -203,7 +160,7 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
mappingMethods.add( getIterableMappingMethod( methods, method ) );
|
||||
}
|
||||
else {
|
||||
mappingMethods.add( getSimpleMappingMethod( methods, method, unmappedTargetPolicy ) );
|
||||
mappingMethods.add( getBeanMappingMethod( methods, method, unmappedTargetPolicy ) );
|
||||
}
|
||||
}
|
||||
return mappingMethods;
|
||||
@ -218,8 +175,8 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
return reversed;
|
||||
}
|
||||
|
||||
private MappingMethod getSimpleMappingMethod(List<Method> methods, Method method,
|
||||
ReportingPolicy unmappedTargetPolicy) {
|
||||
private MappingMethod getBeanMappingMethod(List<Method> methods, Method method,
|
||||
ReportingPolicy unmappedTargetPolicy) {
|
||||
List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
|
||||
Set<String> mappedTargetProperties = new HashSet<String>();
|
||||
|
||||
@ -240,21 +197,21 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
elementUtils.getAllMembers( returnTypeElement )
|
||||
);
|
||||
|
||||
Set<String> sourceProperties = Executables.getPropertyNames(
|
||||
Set<String> sourceProperties = executables.getPropertyNames(
|
||||
Filters.getterMethodsIn( sourceGetters )
|
||||
);
|
||||
Set<String> targetProperties = Executables.getPropertyNames(
|
||||
Set<String> targetProperties = executables.getPropertyNames(
|
||||
Filters.setterMethodsIn( targetSetters )
|
||||
);
|
||||
|
||||
reportErrorIfMappedPropertiesDontExist( method, sourceProperties, targetProperties );
|
||||
|
||||
for ( ExecutableElement getterMethod : sourceGetters ) {
|
||||
String sourcePropertyName = Executables.getPropertyName( getterMethod );
|
||||
String sourcePropertyName = executables.getPropertyName( getterMethod );
|
||||
Mapping mapping = mappings.get( sourcePropertyName );
|
||||
|
||||
for ( ExecutableElement setterMethod : targetSetters ) {
|
||||
String targetPropertyName = Executables.getPropertyName( setterMethod );
|
||||
String targetPropertyName = executables.getPropertyName( setterMethod );
|
||||
|
||||
if ( targetPropertyName.equals( mapping != null ? mapping.getTargetName() : sourcePropertyName ) ) {
|
||||
PropertyMapping property = getPropertyMapping(
|
||||
@ -278,7 +235,7 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
mappedTargetProperties
|
||||
);
|
||||
|
||||
return new SimpleMappingMethod(
|
||||
return new BeanMappingMethod(
|
||||
method.getName(),
|
||||
method.getParameterName(),
|
||||
method.getSourceType(),
|
||||
@ -287,10 +244,65 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
);
|
||||
}
|
||||
|
||||
private void reportErrorForUnmappedTargetPropertiesIfRequired(Method method,
|
||||
ReportingPolicy unmappedTargetPolicy,
|
||||
Set<String> targetProperties,
|
||||
Set<String> mappedTargetProperties) {
|
||||
|
||||
if ( targetProperties.size() > mappedTargetProperties.size() &&
|
||||
unmappedTargetPolicy.requiresReport() ) {
|
||||
targetProperties.removeAll( mappedTargetProperties );
|
||||
printMessage(
|
||||
unmappedTargetPolicy,
|
||||
MessageFormat.format(
|
||||
"Unmapped target {0,choice,1#property|1<properties}: \"{1}\"",
|
||||
targetProperties.size(),
|
||||
Strings.join( targetProperties, ", " )
|
||||
),
|
||||
method.getExecutable()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Method getReverseMappingMethod(List<Method> rawMethods, Method method) {
|
||||
for ( Method oneMethod : rawMethods ) {
|
||||
if ( oneMethod.reverses( method ) ) {
|
||||
return oneMethod;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reportErrorIfMappedPropertiesDontExist(Method method, Set<String> sourceProperties,
|
||||
Set<String> targetProperties) {
|
||||
for ( Mapping mappedProperty : method.getMappings().values() ) {
|
||||
if ( !sourceProperties.contains( mappedProperty.getSourceName() ) ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
String.format(
|
||||
"Unknown property \"%s\" in parameter type %s.",
|
||||
mappedProperty.getSourceName(),
|
||||
method.getSourceType()
|
||||
), method.getExecutable(), mappedProperty.getMirror(), mappedProperty.getSourceAnnotationValue()
|
||||
);
|
||||
}
|
||||
if ( !targetProperties.contains( mappedProperty.getTargetName() ) ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
String.format(
|
||||
"Unknown property \"%s\" in return type %s.",
|
||||
mappedProperty.getTargetName(),
|
||||
method.getTargetType()
|
||||
), method.getExecutable(), mappedProperty.getMirror(), mappedProperty.getTargetAnnotationValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PropertyMapping getPropertyMapping(List<Method> methods, Method method, ExecutableElement getterMethod,
|
||||
ExecutableElement setterMethod) {
|
||||
Type sourceType = retrieveReturnType( getterMethod );
|
||||
Type targetType = retrieveParameter( setterMethod ).getType();
|
||||
Type sourceType = executables.retrieveReturnType( getterMethod );
|
||||
Type targetType = executables.retrieveParameter( setterMethod ).getType();
|
||||
|
||||
MappingMethodReference propertyMappingMethod = getMappingMethodReference( methods, sourceType, targetType );
|
||||
Conversion conversion = conversions.getConversion(
|
||||
@ -301,10 +313,10 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
PropertyMapping property = new PropertyMapping(
|
||||
method.getParameterName(),
|
||||
Introspector.decapitalize( method.getTargetType().getName() ),
|
||||
Executables.getPropertyName( getterMethod ),
|
||||
executables.getPropertyName( getterMethod ),
|
||||
getterMethod.getSimpleName().toString(),
|
||||
sourceType,
|
||||
Executables.getPropertyName( setterMethod ),
|
||||
executables.getPropertyName( setterMethod ),
|
||||
setterMethod.getSimpleName().toString(),
|
||||
targetType,
|
||||
propertyMappingMethod,
|
||||
@ -344,24 +356,35 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
);
|
||||
}
|
||||
|
||||
private void reportErrorForUnmappedTargetPropertiesIfRequired(Method method,
|
||||
ReportingPolicy unmappedTargetPolicy,
|
||||
Set<String> targetProperties,
|
||||
Set<String> mappedTargetProperties) {
|
||||
private String getIterableConversionString(Conversions conversions, Type sourceElementType, Type targetElementType,
|
||||
boolean isToConversion) {
|
||||
Conversion conversion = conversions.getConversion( sourceElementType, targetElementType );
|
||||
|
||||
if ( targetProperties.size() > mappedTargetProperties.size() &&
|
||||
unmappedTargetPolicy.requiresReport() ) {
|
||||
targetProperties.removeAll( mappedTargetProperties );
|
||||
printMessage(
|
||||
unmappedTargetPolicy,
|
||||
MessageFormat.format(
|
||||
"Unmapped target {0,choice,1#property|1<properties}: \"{1}\"",
|
||||
targetProperties.size(),
|
||||
Strings.join( targetProperties, ", " )
|
||||
),
|
||||
method.getExecutable()
|
||||
);
|
||||
if ( conversion == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return conversion.to(
|
||||
Introspector.decapitalize( sourceElementType.getName() ),
|
||||
targetElementType
|
||||
);
|
||||
}
|
||||
|
||||
private MappingMethodReference getMappingMethodReference(Iterable<Method> methods, Type parameterType,
|
||||
Type returnType) {
|
||||
for ( Method oneMethod : methods ) {
|
||||
if ( oneMethod.getSourceType().equals( parameterType ) && oneMethod.getTargetType().equals( returnType ) ) {
|
||||
return new MappingMethodReference(
|
||||
oneMethod.getDeclaringMapper(),
|
||||
oneMethod.getName(),
|
||||
oneMethod.getParameterName(),
|
||||
oneMethod.getSourceType(),
|
||||
oneMethod.getTargetType()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reportErrorIfPropertyCanNotBeMapped(Method method, PropertyMapping property) {
|
||||
@ -390,239 +413,16 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
}
|
||||
}
|
||||
|
||||
private String getIterableConversionString(Conversions conversions, Type sourceElementType, Type targetElementType,
|
||||
boolean isToConversion) {
|
||||
Conversion conversion = conversions.getConversion( sourceElementType, targetElementType );
|
||||
|
||||
if ( conversion == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return conversion.to(
|
||||
Introspector.decapitalize( sourceElementType.getName() ),
|
||||
targetElementType
|
||||
);
|
||||
}
|
||||
|
||||
private List<Type> getUsedMapperTypes(TypeElement element) {
|
||||
List<Type> usedMapperTypes = new LinkedList<Type>();
|
||||
MapperPrism mapperPrism = MapperPrism.getInstanceOn( element );
|
||||
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
|
||||
usedMapperTypes.add( typeUtil.retrieveType( usedMapper ) );
|
||||
}
|
||||
return usedMapperTypes;
|
||||
}
|
||||
|
||||
private MappingMethodReference getMappingMethodReference(Iterable<Method> methods, Type parameterType,
|
||||
Type returnType) {
|
||||
for ( Method oneMethod : methods ) {
|
||||
if ( oneMethod.getSourceType().equals( parameterType ) && oneMethod.getTargetType().equals( returnType ) ) {
|
||||
return new MappingMethodReference(
|
||||
oneMethod.getDeclaringMapper(),
|
||||
oneMethod.getName(),
|
||||
oneMethod.getParameterName(),
|
||||
oneMethod.getSourceType(),
|
||||
oneMethod.getTargetType()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Method getReverseMappingMethod(List<Method> rawMethods, Method method) {
|
||||
for ( Method oneMethod : rawMethods ) {
|
||||
if ( oneMethod.reverses( method ) ) {
|
||||
return oneMethod;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapping methods declared by the given mapper type.
|
||||
*
|
||||
* @param element The type of interest
|
||||
* @param implementationRequired Whether an implementation of this type must be generated or
|
||||
* not. {@code true} if the type is the currently processed
|
||||
* mapper interface, {@code false} if the given type is one
|
||||
* referred to via {@code Mapper#uses()}.
|
||||
*
|
||||
* @return All mapping methods declared by the given type
|
||||
*/
|
||||
private List<Method> retrieveMethods(TypeElement element, boolean implementationRequired) {
|
||||
List<Method> methods = new ArrayList<Method>();
|
||||
|
||||
MapperPrism mapperPrism = implementationRequired ? MapperPrism.getInstanceOn( element ) : null;
|
||||
|
||||
//TODO Extract to separate method
|
||||
for ( ExecutableElement method : methodsIn( element.getEnclosedElements() ) ) {
|
||||
Parameter parameter = retrieveParameter( method );
|
||||
Type returnType = retrieveReturnType( method );
|
||||
|
||||
boolean mappingErroneous = false;
|
||||
|
||||
if ( implementationRequired ) {
|
||||
if ( parameter.getType().isIterableType() && !returnType.isIterableType() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method from iterable type to non-iterable type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
if ( !parameter.getType().isIterableType() && returnType.isIterableType() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method from non-iterable type to iterable type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
if ( parameter.getType().isPrimitive() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method with primitive parameter type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
if ( returnType.isPrimitive() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method with primitive return type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
|
||||
if ( mappingErroneous ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//add method with property mappings if an implementation needs to be generated
|
||||
if ( implementationRequired ) {
|
||||
methods.add(
|
||||
Method.forMethodRequiringImplementation(
|
||||
method,
|
||||
parameter.getName(),
|
||||
parameter.getType(),
|
||||
returnType,
|
||||
getMappings( method )
|
||||
)
|
||||
);
|
||||
}
|
||||
//otherwise add reference to existing mapper method
|
||||
else {
|
||||
methods.add(
|
||||
Method.forReferencedMethod(
|
||||
typeUtil.getType( typeUtils.getDeclaredType( element ) ),
|
||||
method,
|
||||
parameter.getName(),
|
||||
parameter.getType(),
|
||||
returnType
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Add all methods of used mappers in order to reference them in the aggregated model
|
||||
if ( implementationRequired ) {
|
||||
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
|
||||
methods.addAll(
|
||||
retrieveMethods(
|
||||
(TypeElement) ( (DeclaredType) usedMapper ).asElement(),
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
private void reportErrorIfMappedPropertiesDontExist(Method method, Set<String> sourceProperties,
|
||||
Set<String> targetProperties) {
|
||||
for ( Mapping mappedProperty : method.getMappings().values() ) {
|
||||
if ( !sourceProperties.contains( mappedProperty.getSourceName() ) ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
String.format(
|
||||
"Unknown property \"%s\" in parameter type %s.",
|
||||
mappedProperty.getSourceName(),
|
||||
method.getSourceType()
|
||||
), method.getExecutable(), mappedProperty.getMirror(), mappedProperty.getSourceAnnotationValue()
|
||||
);
|
||||
}
|
||||
if ( !targetProperties.contains( mappedProperty.getTargetName() ) ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
String.format(
|
||||
"Unknown property \"%s\" in return type %s.",
|
||||
mappedProperty.getTargetName(),
|
||||
method.getTargetType()
|
||||
), method.getExecutable(), mappedProperty.getMirror(), mappedProperty.getTargetAnnotationValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mappings configured via {@code @Mapping} from the given
|
||||
* method.
|
||||
*
|
||||
* @param method The method of interest
|
||||
*
|
||||
* @return The mappings for the given method, keyed by source property name
|
||||
*/
|
||||
private Map<String, Mapping> getMappings(ExecutableElement method) {
|
||||
Map<String, Mapping> mappings = new HashMap<String, Mapping>();
|
||||
|
||||
MappingPrism mappingAnnotation = MappingPrism.getInstanceOn( method );
|
||||
MappingsPrism mappingsAnnotation = MappingsPrism.getInstanceOn( method );
|
||||
|
||||
if ( mappingAnnotation != null ) {
|
||||
mappings.put( mappingAnnotation.source(), Mapping.fromMappingPrism( mappingAnnotation ) );
|
||||
}
|
||||
|
||||
if ( mappingsAnnotation != null ) {
|
||||
mappings.putAll( Mapping.fromMappingsPrism( mappingsAnnotation ) );
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private Parameter retrieveParameter(ExecutableElement method) {
|
||||
List<? extends VariableElement> parameters = method.getParameters();
|
||||
|
||||
if ( parameters.size() != 1 ) {
|
||||
//TODO: Log error
|
||||
return null;
|
||||
}
|
||||
|
||||
VariableElement parameter = parameters.get( 0 );
|
||||
|
||||
return new Parameter(
|
||||
parameter.getSimpleName().toString(),
|
||||
typeUtil.retrieveType( parameter.asType() )
|
||||
);
|
||||
}
|
||||
|
||||
private Type retrieveReturnType(ExecutableElement method) {
|
||||
return typeUtil.retrieveType( method.getReturnType() );
|
||||
}
|
||||
|
||||
private void printMessage(ReportingPolicy reportingPolicy, String message, Element element) {
|
||||
processingEnvironment.getMessager().printMessage( reportingPolicy.getDiagnosticKind(), message, element );
|
||||
messager.printMessage( reportingPolicy.getDiagnosticKind(), message, element );
|
||||
if ( reportingPolicy.failsBuild() ) {
|
||||
mappingErroneous = true;
|
||||
isErroneous = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void printMessage(ReportingPolicy reportingPolicy, String message, Element element,
|
||||
AnnotationMirror annotationMirror, AnnotationValue annotationValue) {
|
||||
processingEnvironment.getMessager()
|
||||
messager
|
||||
.printMessage(
|
||||
reportingPolicy.getDiagnosticKind(),
|
||||
message,
|
||||
@ -631,7 +431,7 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
|
||||
annotationValue
|
||||
);
|
||||
if ( reportingPolicy.failsBuild() ) {
|
||||
mappingErroneous = true;
|
||||
isErroneous = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright 2012-2013 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.processor;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.annotation.processing.Filer;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import org.mapstruct.ap.model.Mapper;
|
||||
import org.mapstruct.ap.writer.ModelWriter;
|
||||
|
||||
/**
|
||||
* A {@link ModelElementProcessor} which creates a Java source file representing
|
||||
* the given {@link Mapper} object.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
public class MapperRenderingProcessor implements ModelElementProcessor<Mapper, Void> {
|
||||
|
||||
@Override
|
||||
public Void process(ProcessorContext context, TypeElement mapperTypeElement, Mapper sourceElement) {
|
||||
if ( !sourceElement.isErroneous() ) {
|
||||
writeToSourceFile( context.getFiler(), sourceElement );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeToSourceFile(Filer filer, Mapper model) {
|
||||
String fileName = model.getPackageName() + "." + model.getImplementationName();
|
||||
|
||||
JavaFileObject sourceFile;
|
||||
try {
|
||||
sourceFile = filer.createSourceFile( fileName );
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
|
||||
new ModelWriter().writeModel( sourceFile, model );
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Copyright 2012-2013 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.processor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
import org.mapstruct.ap.MapperPrism;
|
||||
import org.mapstruct.ap.MappingPrism;
|
||||
import org.mapstruct.ap.MappingsPrism;
|
||||
import org.mapstruct.ap.model.ReportingPolicy;
|
||||
import org.mapstruct.ap.model.Type;
|
||||
import org.mapstruct.ap.model.source.Mapping;
|
||||
import org.mapstruct.ap.model.source.Method;
|
||||
import org.mapstruct.ap.model.source.Parameter;
|
||||
import org.mapstruct.ap.util.Executables;
|
||||
import org.mapstruct.ap.util.TypeUtil;
|
||||
|
||||
import static javax.lang.model.util.ElementFilter.methodsIn;
|
||||
|
||||
/**
|
||||
* A {@link ModelElementProcessor} which retrieves a list of {@link Method}s
|
||||
* representing all the mapping methods of the given bean mapper type as well as
|
||||
* all referenced mapper methods declared by other mappers referenced by the
|
||||
* current mapper.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
public class MethodRetrievalProcessor implements ModelElementProcessor<TypeElement, List<Method>> {
|
||||
|
||||
private Types typeUtils;
|
||||
private Messager messager;
|
||||
private TypeUtil typeUtil;
|
||||
private Executables executables;
|
||||
|
||||
@Override
|
||||
public List<Method> process(ProcessorContext context, TypeElement mapperTypeElement, TypeElement sourceElement) {
|
||||
this.typeUtils = context.getTypeUtils();
|
||||
this.messager = context.getMessager();
|
||||
this.typeUtil = new TypeUtil( context.getElementUtils(), typeUtils );
|
||||
this.executables = new Executables( typeUtil );
|
||||
|
||||
return retrieveMethods( mapperTypeElement, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mapping methods declared by the given mapper type.
|
||||
*
|
||||
* @param element The type of interest
|
||||
* @param implementationRequired Whether an implementation of this type must be generated or
|
||||
* not. {@code true} if the type is the currently processed
|
||||
* mapper interface, {@code false} if the given type is one
|
||||
* referred to via {@code Mapper#uses()}.
|
||||
*
|
||||
* @return All mapping methods declared by the given type
|
||||
*/
|
||||
private List<Method> retrieveMethods(TypeElement element, boolean implementationRequired) {
|
||||
List<Method> methods = new ArrayList<Method>();
|
||||
|
||||
MapperPrism mapperPrism = implementationRequired ? MapperPrism.getInstanceOn( element ) : null;
|
||||
|
||||
//TODO Extract to separate method
|
||||
for ( ExecutableElement method : methodsIn( element.getEnclosedElements() ) ) {
|
||||
Parameter parameter = executables.retrieveParameter( method );
|
||||
Type returnType = executables.retrieveReturnType( method );
|
||||
|
||||
boolean mappingErroneous = false;
|
||||
|
||||
if ( implementationRequired ) {
|
||||
if ( parameter.getType().isIterableType() && !returnType.isIterableType() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method from iterable type to non-iterable type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
if ( !parameter.getType().isIterableType() && returnType.isIterableType() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method from non-iterable type to iterable type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
if ( parameter.getType().isPrimitive() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method with primitive parameter type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
if ( returnType.isPrimitive() ) {
|
||||
printMessage(
|
||||
ReportingPolicy.ERROR,
|
||||
"Can't generate mapping method with primitive return type.",
|
||||
method
|
||||
);
|
||||
mappingErroneous = true;
|
||||
}
|
||||
|
||||
if ( mappingErroneous ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//add method with property mappings if an implementation needs to be generated
|
||||
if ( implementationRequired ) {
|
||||
methods.add(
|
||||
Method.forMethodRequiringImplementation(
|
||||
method,
|
||||
parameter.getName(),
|
||||
parameter.getType(),
|
||||
returnType,
|
||||
getMappings( method )
|
||||
)
|
||||
);
|
||||
}
|
||||
//otherwise add reference to existing mapper method
|
||||
else {
|
||||
methods.add(
|
||||
Method.forReferencedMethod(
|
||||
typeUtil.getType( typeUtils.getDeclaredType( element ) ),
|
||||
method,
|
||||
parameter.getName(),
|
||||
parameter.getType(),
|
||||
returnType
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Add all methods of used mappers in order to reference them in the aggregated model
|
||||
if ( implementationRequired ) {
|
||||
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
|
||||
methods.addAll(
|
||||
retrieveMethods(
|
||||
(TypeElement) ( (DeclaredType) usedMapper ).asElement(),
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mappings configured via {@code @Mapping} from the given
|
||||
* method.
|
||||
*
|
||||
* @param method The method of interest
|
||||
*
|
||||
* @return The mappings for the given method, keyed by source property name
|
||||
*/
|
||||
private Map<String, Mapping> getMappings(ExecutableElement method) {
|
||||
Map<String, Mapping> mappings = new HashMap<String, Mapping>();
|
||||
|
||||
MappingPrism mappingAnnotation = MappingPrism.getInstanceOn( method );
|
||||
MappingsPrism mappingsAnnotation = MappingsPrism.getInstanceOn( method );
|
||||
|
||||
if ( mappingAnnotation != null ) {
|
||||
mappings.put( mappingAnnotation.source(), Mapping.fromMappingPrism( mappingAnnotation ) );
|
||||
}
|
||||
|
||||
if ( mappingsAnnotation != null ) {
|
||||
mappings.putAll( Mapping.fromMappingsPrism( mappingsAnnotation ) );
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private void printMessage(ReportingPolicy reportingPolicy, String message, Element element) {
|
||||
messager.printMessage( reportingPolicy.getDiagnosticKind(), message, element );
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright 2012-2013 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.processor;
|
||||
|
||||
import javax.annotation.processing.Filer;
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
|
||||
import org.mapstruct.ap.model.Options;
|
||||
|
||||
/**
|
||||
* A processor which performs one task of the mapper generation, e.g. retrieving
|
||||
* methods from the source {@link TypeElement}, performing validity checks or
|
||||
* generating the output source file.
|
||||
*
|
||||
* @param <P> The parameter type processed by this processor
|
||||
* @param <R> The return type created by this processor
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
public interface ModelElementProcessor<P, R> {
|
||||
|
||||
/**
|
||||
* Context object passed to
|
||||
* {@link ModelElementProcessor#process(ProcessorContext, TypeElement, Object)}
|
||||
* providing access to common infrastructure objects such as {@link Types}
|
||||
* etc.
|
||||
*
|
||||
* @author Gunnar Morling
|
||||
*/
|
||||
public interface ProcessorContext {
|
||||
|
||||
Filer getFiler();
|
||||
|
||||
Types getTypeUtils();
|
||||
|
||||
Elements getElementUtils();
|
||||
|
||||
Messager getMessager();
|
||||
|
||||
Options getOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the given source element, representing a Java bean mapper in
|
||||
* one form or another.
|
||||
*
|
||||
* @param context Context providing common infrastructure objects.
|
||||
* @param mapperTypeElement The original type element from which the given mapper object
|
||||
* is derived.
|
||||
* @param sourceElement The current representation of the bean mapper. Never
|
||||
* {@code null} (the very first processor receives the original
|
||||
* type element).
|
||||
*
|
||||
* @return The resulting representation of the bean mapper; may be the same
|
||||
* as the source representation, e.g. if a given implementation just
|
||||
* performs some sort of validity check. Implementations must never
|
||||
* return {@code null} except for the very last processor which
|
||||
* generates the resulting Java source file.
|
||||
*/
|
||||
R process(ProcessorContext context, TypeElement mapperTypeElement, P sourceElement);
|
||||
}
|
@ -23,8 +23,12 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
|
||||
import org.mapstruct.ap.model.Type;
|
||||
import org.mapstruct.ap.model.source.Parameter;
|
||||
|
||||
/**
|
||||
* Provides functionality around {@link ExecutableElement}s.
|
||||
*
|
||||
@ -32,14 +36,17 @@ import javax.lang.model.type.TypeKind;
|
||||
*/
|
||||
public class Executables {
|
||||
|
||||
private Executables() {
|
||||
private final TypeUtil typeUtil;
|
||||
|
||||
public Executables(TypeUtil typeUtil) {
|
||||
this.typeUtil = typeUtil;
|
||||
}
|
||||
|
||||
public static boolean isGetterMethod(ExecutableElement method) {
|
||||
public boolean isGetterMethod(ExecutableElement method) {
|
||||
return isNonBooleanGetterMethod( method ) || isBooleanGetterMethod( method );
|
||||
}
|
||||
|
||||
private static boolean isNonBooleanGetterMethod(ExecutableElement method) {
|
||||
private boolean isNonBooleanGetterMethod(ExecutableElement method) {
|
||||
String name = method.getSimpleName().toString();
|
||||
|
||||
return method.getParameters().isEmpty() &&
|
||||
@ -48,7 +55,7 @@ public class Executables {
|
||||
method.getReturnType().getKind() != TypeKind.VOID;
|
||||
}
|
||||
|
||||
private static boolean isBooleanGetterMethod(ExecutableElement method) {
|
||||
private boolean isBooleanGetterMethod(ExecutableElement method) {
|
||||
String name = method.getSimpleName().toString();
|
||||
|
||||
return method.getParameters().isEmpty() &&
|
||||
@ -57,7 +64,7 @@ public class Executables {
|
||||
method.getReturnType().getKind() == TypeKind.BOOLEAN;
|
||||
}
|
||||
|
||||
public static boolean isSetterMethod(ExecutableElement method) {
|
||||
public boolean isSetterMethod(ExecutableElement method) {
|
||||
String name = method.getSimpleName().toString();
|
||||
|
||||
if ( name.startsWith( "set" ) && name.length() > 3 && method.getParameters()
|
||||
@ -68,7 +75,7 @@ public class Executables {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getPropertyName(ExecutableElement getterOrSetterMethod) {
|
||||
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
|
||||
if ( isNonBooleanGetterMethod( getterOrSetterMethod ) ) {
|
||||
return Introspector.decapitalize(
|
||||
getterOrSetterMethod.getSimpleName().toString().substring( 3 )
|
||||
@ -88,13 +95,33 @@ public class Executables {
|
||||
throw new IllegalArgumentException( "Executable " + getterOrSetterMethod + " is not getter or setter method." );
|
||||
}
|
||||
|
||||
public static Set<String> getPropertyNames(List<ExecutableElement> propertyAccessors) {
|
||||
public Set<String> getPropertyNames(List<ExecutableElement> propertyAccessors) {
|
||||
Set<String> propertyNames = new HashSet<String>();
|
||||
|
||||
for ( ExecutableElement executableElement : propertyAccessors ) {
|
||||
propertyNames.add( Executables.getPropertyName( executableElement ) );
|
||||
propertyNames.add( getPropertyName( executableElement ) );
|
||||
}
|
||||
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
public Parameter retrieveParameter(ExecutableElement method) {
|
||||
List<? extends VariableElement> parameters = method.getParameters();
|
||||
|
||||
if ( parameters.size() != 1 ) {
|
||||
//TODO: Log error
|
||||
return null;
|
||||
}
|
||||
|
||||
VariableElement parameter = parameters.get( 0 );
|
||||
|
||||
return new Parameter(
|
||||
parameter.getSimpleName().toString(),
|
||||
typeUtil.retrieveType( parameter.asType() )
|
||||
);
|
||||
}
|
||||
|
||||
public Type retrieveReturnType(ExecutableElement method) {
|
||||
return typeUtil.retrieveType( method.getReturnType() );
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,9 @@ import static javax.lang.model.util.ElementFilter.methodsIn;
|
||||
*/
|
||||
public class Filters {
|
||||
|
||||
//TODO
|
||||
private static Executables executables = new Executables( null );
|
||||
|
||||
private Filters() {
|
||||
}
|
||||
|
||||
@ -39,7 +42,7 @@ public class Filters {
|
||||
List<ExecutableElement> getterMethods = new LinkedList<ExecutableElement>();
|
||||
|
||||
for ( ExecutableElement method : methodsIn( elements ) ) {
|
||||
if ( Executables.isGetterMethod( method ) ) {
|
||||
if ( executables.isGetterMethod( method ) ) {
|
||||
getterMethods.add( method );
|
||||
}
|
||||
}
|
||||
@ -51,7 +54,7 @@ public class Filters {
|
||||
List<ExecutableElement> setterMethods = new LinkedList<ExecutableElement>();
|
||||
|
||||
for ( ExecutableElement method : methodsIn( elements ) ) {
|
||||
if ( Executables.isSetterMethod( method ) ) {
|
||||
if ( executables.isSetterMethod( method ) ) {
|
||||
setterMethods.add( method );
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user