#302 Making MappingContext immutable

This commit is contained in:
Gunnar Morling 2014-10-12 18:23:05 +02:00
parent d42216ee4f
commit 9adbb423c6
4 changed files with 167 additions and 198 deletions

View File

@ -19,8 +19,6 @@
package org.mapstruct.ap.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.Messager;
@ -28,11 +26,13 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.common.TypeFactory;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.option.Options;
import org.mapstruct.ap.prism.MapperPrism;
import org.mapstruct.ap.util.MapperConfig;
/**
* This class provides the context for the builders.
@ -61,6 +61,53 @@ import org.mapstruct.ap.util.MapperConfig;
*/
public class MappingContext {
/**
* Resolves the most suitable way for mapping an element (property, iterable element etc.) from source to target.
* There are 2 basic types of mappings:
* <ul>
* <li>conversions</li>
* <li>methods</li>
* </ul>
* conversions are essentially one line mappings, such as String to Integer and Integer to Long methods come in some
* varieties:
* <ul>
* <li>referenced mapping methods, these are methods implemented (or referenced) by the user. Sometimes indicated
* with the 'uses' in the mapping annotations or part of the abstract mapper class</li>
* <li>generated mapping methods (by means of MapStruct)</li>
* <li>built in methods</li>
* </ul>
*
* @author Sjaak Derksen
*/
public interface MappingResolver {
/**
* returns a parameter assignment
*
* @param mappingMethod target mapping method
* @param mappedElement used for error messages
* @param sourceType parameter to match
* @param targetType return type to match
* @param targetPropertyName name of the target property
* @param dateFormat used for formatting dates in build in methods that need context information
* @param qualifiers used for further select the appropriate mapping method based on class and name
* @param sourceReference call to source type as string
*
* @return an assignment to a method parameter, which can either be:
* <ol>
* <li>MethodReference</li>
* <li>TypeConversion</li>
* <li>Direct Assignment (empty TargetAssignment)</li>
* <li>null, no assignment found</li>
* </ol>
*/
Assignment getTargetAssignment(Method mappingMethod, String mappedElement, Type sourceType, Type targetType,
String targetPropertyName, String dateFormat, List<TypeMirror> qualifiers,
String sourceReference);
Set<VirtualMappingMethod> getUsedVirtualMappings();
}
private final TypeFactory typeFactory;
private final Elements elementUtils;
private final Types typeUtils;
@ -69,32 +116,27 @@ public class MappingContext {
private final TypeElement mapperTypeElement;
private final List<SourceMethod> sourceModel;
private final List<MapperReference> mapperReferences;
private MappingResolver mappingResolver;
private final MappingResolver mappingResolver;
private final List<MappingMethod> mappingsToGenerate = new ArrayList<MappingMethod>();
/**
* Private methods which are not present in the original mapper interface and are added to map certain property
* types.
*/
private final Set<VirtualMappingMethod> usedVirtualMappings = new HashSet<VirtualMappingMethod>();
public MappingContext( TypeFactory typeFactory,
public MappingContext(TypeFactory typeFactory,
Elements elementUtils,
Types typeUtils,
Messager messager,
Options options,
MappingResolver mappingResolver,
TypeElement mapper,
List<SourceMethod> sourceModel ) {
List<SourceMethod> sourceModel,
List<MapperReference> mapperReferences) {
this.typeFactory = typeFactory;
this.elementUtils = elementUtils;
this.typeUtils = typeUtils;
this.messager = messager;
this.options = options;
this.mappingResolver = mappingResolver;
this.mapperTypeElement = mapper;
this.sourceModel = sourceModel;
this.mapperReferences = initReferencedMappers( mapper );
this.mapperReferences = mapperReferences;
}
public TypeElement getMapperTypeElement() {
@ -133,39 +175,11 @@ public class MappingContext {
return mappingResolver;
}
public void setMappingResolver(MappingResolver mappingResolver) {
this.mappingResolver = mappingResolver;
}
public List<MappingMethod> getMappingsToGenerate() {
return mappingsToGenerate;
}
public Set<VirtualMappingMethod> getUsedVirtualMappings() {
return usedVirtualMappings;
return mappingResolver.getUsedVirtualMappings();
}
private List<MapperReference> initReferencedMappers(TypeElement element) {
List<MapperReference> result = new LinkedList<MapperReference>();
List<String> variableNames = new LinkedList<String>();
MapperConfig mapperPrism = MapperConfig.getInstanceOn( element );
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
DefaultMapperReference mapperReference = DefaultMapperReference.getInstance(
typeFactory.getType( usedMapper ),
MapperPrism.getInstanceOn( typeUtils.asElement( usedMapper ) ) != null,
typeFactory,
variableNames
);
result.add( mapperReference );
variableNames.add( mapperReference.getVariableName() );
}
return result;
}
}

View File

@ -1,63 +0,0 @@
/**
* Copyright 2012-2014 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;
import org.mapstruct.ap.model.assignment.Assignment;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
/**
*
* @author Sjaak Derksen
*/
public interface MappingResolver {
/**
* returns a parameter assignment
*
* @param mappingMethod target mapping method
* @param mappedElement used for error messages
* @param sourceType parameter to match
* @param targetType return type to match
* @param targetPropertyName name of the target property
* @param dateFormat used for formatting dates in build in methods that need context information
* @param qualifiers used for further select the appropriate mapping method based on class and name
* @param sourceReference call to source type as string
*
* @return an assignment to a method parameter, which can either be:
* <ol>
* <li>MethodReference</li>
* <li>TypeConversion</li>
* <li>Direct Assignment (empty TargetAssignment)</li>
* <li>null, no assignment found</li>
* </ol>
*/
Assignment getTargetAssignment(
Method mappingMethod,
String mappedElement,
Type sourceType,
Type targetType,
String targetPropertyName,
String dateFormat,
List<TypeMirror> qualifiers,
String sourceReference );
}

View File

@ -20,10 +20,12 @@ package org.mapstruct.ap.processor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.annotation.processing.Messager;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
@ -33,14 +35,17 @@ import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.model.BeanMappingMethod;
import org.mapstruct.ap.model.Decorator;
import org.mapstruct.ap.model.DefaultMapperReference;
import org.mapstruct.ap.model.DelegatingMethod;
import org.mapstruct.ap.model.EnumMappingMethod;
import org.mapstruct.ap.model.IterableMappingMethod;
import org.mapstruct.ap.model.MapMappingMethod;
import org.mapstruct.ap.model.Mapper;
import org.mapstruct.ap.model.MapperReference;
import org.mapstruct.ap.model.MappingContext;
import org.mapstruct.ap.model.MappingMethod;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.common.TypeFactory;
@ -48,7 +53,7 @@ import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.option.Options;
import org.mapstruct.ap.prism.DecoratedWithPrism;
import org.mapstruct.ap.model.MappingContext;
import org.mapstruct.ap.prism.MapperPrism;
import org.mapstruct.ap.processor.creation.MappingResolverImpl;
import org.mapstruct.ap.util.MapperConfig;
@ -60,7 +65,6 @@ import org.mapstruct.ap.util.MapperConfig;
*/
public class MapperCreationProcessor implements ModelElementProcessor<List<SourceMethod>, Mapper> {
private Elements elementUtils;
private Types typeUtils;
private Messager messager;
@ -75,16 +79,20 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
this.messager = context.getMessager();
this.options = context.getOptions();
this.typeFactory = context.getTypeFactory();
List<MapperReference> mapperReferences = initReferencedMappers( mapperTypeElement );
MappingContext ctx = new MappingContext(
typeFactory,
elementUtils,
typeUtils,
messager,
options,
new MappingResolverImpl( context.getMessager(), elementUtils, typeUtils, typeFactory, sourceModel, mapperReferences ),
mapperTypeElement,
sourceModel
sourceModel,
mapperReferences
);
ctx.setMappingResolver( new MappingResolverImpl( ctx ) );
this.mappingContext = ctx;
return getMapper( mapperTypeElement, sourceModel );
}
@ -94,6 +102,27 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
return 1000;
}
private List<MapperReference> initReferencedMappers(TypeElement element) {
List<MapperReference> result = new LinkedList<MapperReference>();
List<String> variableNames = new LinkedList<String>();
MapperConfig mapperPrism = MapperConfig.getInstanceOn( element );
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
DefaultMapperReference mapperReference = DefaultMapperReference.getInstance(
typeFactory.getType( usedMapper ),
MapperPrism.getInstanceOn( typeUtils.asElement( usedMapper ) ) != null,
typeFactory,
variableNames
);
result.add( mapperReference );
variableNames.add( mapperReference.getVariableName() );
}
return result;
}
private Mapper getMapper(TypeElement element, List<SourceMethod> methods) {
List<MapperReference> mapperReferences = mappingContext.getMapperReferences();
List<MappingMethod> mappingMethods = getMappingMethods( methods );

View File

@ -18,26 +18,29 @@
*/
package org.mapstruct.ap.processor.creation;
import org.mapstruct.ap.model.MappingResolver;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.Messager;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.conversion.ConversionProvider;
import org.mapstruct.ap.conversion.Conversions;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.MapperReference;
import org.mapstruct.ap.model.MappingContext;
import org.mapstruct.ap.model.VirtualMappingMethod;
import org.mapstruct.ap.model.AssignmentFactory;
import org.mapstruct.ap.model.Direct;
import org.mapstruct.ap.model.MapperReference;
import org.mapstruct.ap.model.MappingContext.MappingResolver;
import org.mapstruct.ap.model.VirtualMappingMethod;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.DefaultConversionContext;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.common.TypeFactory;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.builtin.BuiltInMappingMethods;
@ -46,50 +49,47 @@ import org.mapstruct.ap.model.source.selector.MethodSelectors;
import org.mapstruct.ap.util.Strings;
/**
* Resolves class is responsible for resolving the most suitable way to resolve a mapping from source to target.
*
* There are 2 basic types of mappings:
* <ul>
* <li>conversions</li>
* <li>methods</li>
* </ul>
*
* conversions are essentially one line mappings, such as String to Integer and Integer to Long
* methods come in some varieties:
* <ul>
* <li>referenced mapping methods, these are methods implemented (or referenced) by the user. Sometimes indicated
* with the 'uses' in the mapping annotations or part of the abstract mapper class</li>
* <li>generated mapping methods (by means of MapStruct)</li>
* <li>built in methods</li>
* </ul>
* The one and only implementation of {@link MappingResolver}. The class has been split into an interface an
* implementation for the sake of avoiding package dependencies. Specifically, this implementation refers to classes
* which should not be exposed to the {@code model} package.
*
* @author Sjaak Derksen
*/
public class MappingResolverImpl implements MappingResolver {
private final Messager messager;
private final Types typeUtils;
private final TypeFactory typeFactory;
private final List<SourceMethod> sourceModel;
private final List<MapperReference> mapperReferences;
private final Conversions conversions;
private final BuiltInMappingMethods builtInMethods;
private final MethodSelectors methodSelectors;
private final MappingContext mappingContext;
/**
* Private methods which are not present in the original mapper interface and are added to map certain property
types.
* @param mappingContext
* types.
*/
public MappingResolverImpl( MappingContext mappingContext ) {
this.conversions = new Conversions( mappingContext.getElementUtils(), mappingContext.getTypeFactory() );
this.builtInMethods = new BuiltInMappingMethods( mappingContext.getTypeFactory() );
this.methodSelectors = new MethodSelectors(
mappingContext.getTypeUtils(),
mappingContext.getElementUtils(),
mappingContext.getTypeFactory()
);
this.mappingContext = mappingContext;
}
private final Set<VirtualMappingMethod> usedVirtualMappings = new HashSet<VirtualMappingMethod>();
public MappingResolverImpl(Messager messager, Elements elementUtils, Types typeUtils, TypeFactory typeFactory, List<SourceMethod> sourceModel, List<MapperReference> mapperReferences) {
this.messager = messager;
this.typeUtils = typeUtils;
this.typeFactory = typeFactory;
this.sourceModel = sourceModel;
this.mapperReferences = mapperReferences;
this.conversions = new Conversions( elementUtils, typeFactory );
this.builtInMethods = new BuiltInMappingMethods( typeFactory );
this.methodSelectors = new MethodSelectors(
typeUtils,
elementUtils,
typeFactory
);
}
/**
* returns a parameter assignment
@ -122,74 +122,64 @@ public class MappingResolverImpl implements MappingResolver {
List<TypeMirror> qualifiers,
String sourceReference ) {
ResolvingAttempt attempt = new ResolvingAttempt( mappingMethod,
ResolvingAttempt attempt = new ResolvingAttempt( sourceModel,
mapperReferences,
mappingMethod,
mappedElement,
targetPropertyName,
dateFormat,
qualifiers,
sourceReference,
conversions,
builtInMethods,
methodSelectors,
mappingContext
sourceReference
);
return attempt.getTargetAssignment( sourceType, targetType );
}
@Override
public Set<VirtualMappingMethod> getUsedVirtualMappings() {
return usedVirtualMappings;
}
private static class ResolvingAttempt {
private class ResolvingAttempt {
private final Method mappingMethod;
private final String mappedElement;
private final List<MapperReference> mapperReferences;
private final List<SourceMethod> methods;
private final String targetPropertyName;
private final String dateFormat;
private final List<TypeMirror> qualifiers;
private final String sourceReference;
private final Conversions conversions;
private final BuiltInMappingMethods builtInMethods;
private final MethodSelectors methodSelectors;
private final MappingContext mappingContext;
// resolving via 2 steps creates the possibillity of wrong matches, first builtin method matches,
// second doesn't. In that case, the first builtin method should not lead to a virtual method
// so this set must be cleared.
private final Set<VirtualMappingMethod> virtualMethodCandidates;
private ResolvingAttempt( Method mappingMethod,
private ResolvingAttempt( List<SourceMethod> sourceModel,
List<MapperReference> mapperReferences,
Method mappingMethod,
String mappedElement,
String targetPropertyName,
String dateFormat,
List<TypeMirror> qualifiers,
String sourceReference,
Conversions conversions,
BuiltInMappingMethods builtInMethods,
MethodSelectors methodSelectors,
MappingContext mappingContext ) {
String sourceReference ) {
this.mappingMethod = mappingMethod;
this.mappedElement = mappedElement;
this.mapperReferences = mappingContext.getMapperReferences();
this.methods = mappingContext.getSourceModel();
this.methods = sourceModel;
this.targetPropertyName = targetPropertyName;
this.dateFormat = dateFormat;
this.qualifiers = qualifiers;
this.sourceReference = sourceReference;
this.conversions = conversions;
this.builtInMethods = builtInMethods;
this.methodSelectors = methodSelectors;
this.mappingContext = mappingContext;
this.virtualMethodCandidates = new HashSet<VirtualMappingMethod>();
}
private Assignment getTargetAssignment( Type sourceType, Type targetType ) {
// first simpele mapping method
// first simple mapping method
Assignment referencedMethod = resolveViaMethod( sourceType, targetType );
if ( referencedMethod != null ) {
referencedMethod.setAssignment( AssignmentFactory.createSimple( sourceReference ) );
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
usedVirtualMappings.addAll( virtualMethodCandidates );
return referencedMethod;
}
@ -209,21 +199,21 @@ public class MappingResolverImpl implements MappingResolver {
// 2 step method, first: method(method(souurce))
referencedMethod = resolveViaMethodAndMethod( sourceType, targetType );
if ( referencedMethod != null ) {
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
usedVirtualMappings.addAll( virtualMethodCandidates );
return referencedMethod;
}
// 2 step method, then: method(conversion(souurce))
referencedMethod = resolveViaConversionAndMethod( sourceType, targetType );
if ( referencedMethod != null ) {
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
usedVirtualMappings.addAll( virtualMethodCandidates );
return referencedMethod;
}
// 2 step method, finally: conversion(method(souurce))
conversion = resolveViaMethodAndConversion( sourceType, targetType );
if ( conversion != null ) {
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
usedVirtualMappings.addAll( virtualMethodCandidates );
return conversion;
}
@ -239,7 +229,7 @@ public class MappingResolverImpl implements MappingResolver {
}
ConversionContext ctx =
new DefaultConversionContext( mappingContext.getTypeFactory(), targetType, dateFormat );
new DefaultConversionContext( typeFactory, targetType, dateFormat );
return conversionProvider.to( ctx );
}
@ -254,7 +244,7 @@ public class MappingResolverImpl implements MappingResolver {
SourceMethod matchingSourceMethod = getBestMatch( methods, sourceType, targetType );
if ( matchingSourceMethod != null ) {
return getMappingMethodReference( matchingSourceMethod, mapperReferences, targetType );
return getMappingMethodReference( matchingSourceMethod, targetType );
}
// then a matching built-in method
@ -264,7 +254,7 @@ public class MappingResolverImpl implements MappingResolver {
if ( matchingBuiltInMethod != null ) {
virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod ) );
ConversionContext ctx =
new DefaultConversionContext( mappingContext.getTypeFactory(), targetType, dateFormat );
new DefaultConversionContext( typeFactory, targetType, dateFormat );
Assignment methodReference = AssignmentFactory.createMethodReference( matchingBuiltInMethod, ctx );
methodReference.setAssignment( AssignmentFactory.createSimple( sourceReference ) );
return methodReference;
@ -424,7 +414,7 @@ public class MappingResolverImpl implements MappingResolver {
returnType,
Strings.join( candidates, ", " ) );
mappingMethod.printMessage( mappingContext.getMessager(), Kind.ERROR, errorMsg );
mappingMethod.printMessage( messager, Kind.ERROR, errorMsg );
}
if ( !candidates.isEmpty() ) {
@ -435,9 +425,8 @@ public class MappingResolverImpl implements MappingResolver {
}
private Assignment getMappingMethodReference( SourceMethod method,
List<MapperReference> mapperReferences,
Type targetType ) {
MapperReference mapperReference = findMapperReference( mapperReferences, method );
MapperReference mapperReference = findMapperReference( method );
return AssignmentFactory.createMethodReference(
method,
@ -446,7 +435,7 @@ public class MappingResolverImpl implements MappingResolver {
);
}
private MapperReference findMapperReference( List<MapperReference> mapperReferences, SourceMethod method ) {
private MapperReference findMapperReference( SourceMethod method ) {
for ( MapperReference ref : mapperReferences ) {
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {
return ref;
@ -513,14 +502,14 @@ public class MappingResolverImpl implements MappingResolver {
// constructor which accepts the source type
TypeMirror sourceElementType = sourceType.getTypeParameters().isEmpty()
? mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror()
? typeFactory.getType( Object.class ).getTypeMirror()
: sourceType.getTypeParameters().get( 0 ).getTypeMirror();
TypeMirror targetElementType = targetType.getTypeParameters().isEmpty()
? mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror()
? typeFactory.getType( Object.class ).getTypeMirror()
: targetType.getTypeParameters().get( 0 ).getTypeMirror();
return mappingContext.getTypeUtils().isAssignable( sourceElementType, targetElementType );
return typeUtils.isAssignable( sourceElementType, targetElementType );
}
/**
@ -544,8 +533,8 @@ public class MappingResolverImpl implements MappingResolver {
TypeMirror targetValueType;
if ( sourceType.getTypeParameters().isEmpty() ) {
sourceKeyType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror();
sourceValueType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror();
sourceKeyType = typeFactory.getType( Object.class ).getTypeMirror();
sourceValueType = typeFactory.getType( Object.class ).getTypeMirror();
}
else {
sourceKeyType = sourceType.getTypeParameters().get( 0 ).getTypeMirror();
@ -553,16 +542,16 @@ public class MappingResolverImpl implements MappingResolver {
}
if ( targetType.getTypeParameters().isEmpty() ) {
targetKeyType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror();
targetValueType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror();
targetKeyType = typeFactory.getType( Object.class ).getTypeMirror();
targetValueType = typeFactory.getType( Object.class ).getTypeMirror();
}
else {
targetKeyType = targetType.getTypeParameters().get( 0 ).getTypeMirror();
targetValueType = targetType.getTypeParameters().get( 1 ).getTypeMirror();
}
return mappingContext.getTypeUtils().isAssignable( sourceKeyType, targetKeyType )
&& mappingContext.getTypeUtils().isAssignable( sourceValueType, targetValueType );
return typeUtils.isAssignable( sourceKeyType, targetKeyType )
&& typeUtils.isAssignable( sourceValueType, targetValueType );
}
}
}