#302 refactoring of MappingCreationProcessor: using Builders to address smaller concerns

This commit is contained in:
sjaakd 2014-09-29 20:53:13 +02:00
parent 3e411e085e
commit d42216ee4f
30 changed files with 1669 additions and 1270 deletions

View File

@ -18,8 +18,8 @@
*/
package org.mapstruct.ap.conversion;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.assignment.TypeConversion;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.TypeConversion;
import org.mapstruct.ap.model.common.ConversionContext;
/**

View File

@ -23,8 +23,8 @@ import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.assignment.AssignmentFactory;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.AssignmentFactory;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.Type;

View File

@ -18,7 +18,7 @@
*/
package org.mapstruct.ap.conversion;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.common.ConversionContext;
/**

View File

@ -21,9 +21,9 @@ package org.mapstruct.ap.conversion;
import java.util.Collections;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.assignment.AssignmentFactory;
import org.mapstruct.ap.model.assignment.TypeConversion;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.AssignmentFactory;
import org.mapstruct.ap.model.TypeConversion;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.Type;

View File

@ -0,0 +1,103 @@
/**
* 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 java.util.Set;
import javax.tools.Diagnostic;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.builtin.BuiltInMethod;
import org.mapstruct.ap.model.source.selector.MethodSelectors;
/**
* Factory class for creating all types of assignments
*
* @author Sjaak Derksen
*/
public class AssignmentFactory {
private AssignmentFactory() {
}
public static Assignment createTypeConversion(Set<Type> importTypes, List<Type> exceptionTypes, String expression) {
return new TypeConversion( importTypes, exceptionTypes, expression );
}
public static Assignment createMethodReference(Method method, MapperReference declaringMapper,
Type targetType) {
return new MethodReference( method, declaringMapper, targetType );
}
public static Assignment createMethodReference(BuiltInMethod method, ConversionContext contextParam) {
return new MethodReference( method, contextParam );
}
public static Direct createSimple(String sourceRef) {
return new Direct( sourceRef );
}
public static FactoryMethod createFactoryMethod( Type returnType, MappingContext ctx ) {
FactoryMethod result = null;
for ( SourceMethod method : ctx.getSourceModel() ) {
if ( !method.overridesMethod() && !method.isIterableMapping() && !method.isMapMapping()
&& method.getSourceParameters().isEmpty() ) {
List<Type> parameterTypes = MethodSelectors.getParameterTypes(
ctx.getTypeFactory(),
method.getParameters(),
null,
returnType
);
if ( method.matches( parameterTypes, returnType ) ) {
if ( result == null ) {
MapperReference mapperReference = findMapperReference( ctx.getMapperReferences(), method );
result = new MethodReference( method, mapperReference, null );
}
else {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Ambiguous factory methods: \"%s\" conflicts with \"%s\".",
result,
method
),
method.getExecutable()
);
}
}
}
}
return result;
}
private static MapperReference findMapperReference( List<MapperReference> mapperReferences, SourceMethod method ) {
for ( MapperReference ref : mapperReferences ) {
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {
return ref;
}
}
return null;
}
}

View File

@ -18,15 +18,25 @@
*/
package org.mapstruct.ap.model;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.tools.Diagnostic;
import org.mapstruct.CollectionMappingStrategy;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.option.ReportingPolicy;
import org.mapstruct.ap.util.Executables;
import org.mapstruct.ap.util.MapperConfig;
import org.mapstruct.ap.util.Strings;
/**
* A {@link MappingMethod} implemented by a {@link Mapper} class which maps one
@ -44,7 +54,336 @@ public class BeanMappingMethod extends MappingMethod {
private final FactoryMethod factoryMethod;
public BeanMappingMethod(SourceMethod method,
public static class Builder {
private MappingContext ctx;
private SourceMethod method;
public Builder mappingContext(MappingContext mappingContext) {
this.ctx = mappingContext;
return this;
}
public Builder souceMethod( SourceMethod sourceMethod ) {
this.method = sourceMethod;
return this;
}
public BeanMappingMethod build() {
// fetch settings from element to implement
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy();
CollectionMappingStrategy cmStrategy = getEffectiveCollectionMappingStrategy();
List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
Set<String> mappedTargetProperties = new HashSet<String>();
Set<String> ignoredTargetProperties = new HashSet<String>();
if ( !reportErrorIfMappedPropertiesDontExist() ) {
return null;
}
// collect all target accessors
List<ExecutableElement> targetAccessors = new ArrayList<ExecutableElement>();
targetAccessors.addAll( method.getResultType().getSetters() );
targetAccessors.addAll( method.getResultType().getAlternativeTargetAccessors() );
for ( ExecutableElement targetAccessor : targetAccessors ) {
String targetPropertyName = Executables.getPropertyName( targetAccessor );
Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName );
if ( mapping != null && mapping.isIgnored() ) {
ignoredTargetProperties.add( targetPropertyName );
continue;
}
// A target access is in general a setter method on the target object. However, in case of collections,
// the current target accessor can also be a getter method.
// The following if block, checks if the target accessor should be overruled by an add method.
if ( cmStrategy.equals( CollectionMappingStrategy.SETTER_PREFERRED )
|| cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) {
// first check if there's a setter method.
ExecutableElement adderMethod = null;
if ( Executables.isSetterMethod( targetAccessor ) ) {
Type targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType();
// ok, the current accessor is a setter. So now the strategy determines what to use
if ( cmStrategy.equals( CollectionMappingStrategy.ADDER_PREFERRED ) ) {
adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName );
}
}
else if ( Executables.isGetterMethod( targetAccessor ) ) {
// the current accessor is a getter (no setter available). But still, an add method is according
// to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter.
Type targetType = ctx.getTypeFactory().getReturnType( targetAccessor );
adderMethod = method.getResultType().getAdderForType( targetType, targetPropertyName );
}
if ( adderMethod != null ) {
// an adder has been found (according strategy) so overrule current choice.
targetAccessor = adderMethod;
}
}
PropertyMapping propertyMapping = null;
if ( mapping != null ) {
if ( mapping.getSourceParameterName() != null ) {
// this is a parameterized property, so sourceParameter.property
Parameter parameter = method.getSourceParameter( mapping.getSourceParameterName() );
PropertyMapping.PropertyMappingBuilder builder = new PropertyMapping.PropertyMappingBuilder();
propertyMapping = builder
.mappingContext( ctx )
.souceMethod( method )
.targetAccessor( targetAccessor )
.targetPropertyName( targetPropertyName )
.parameter( parameter )
.build();
}
else if ( Executables.isSetterMethod( targetAccessor )
|| Executables.isGetterMethod( targetAccessor ) ) {
if ( !mapping.getConstant().isEmpty() ) {
// its a constant
PropertyMapping.ConstantMappingBuilder builder =
new PropertyMapping.ConstantMappingBuilder();
propertyMapping = builder
.mappingContext( ctx )
.sourceMethod( method )
.constantExpression( "\"" + mapping.getConstant() + "\"" )
.targetAccessor( targetAccessor )
.dateFormat( mapping.getDateFormat() )
.qualifiers( mapping.getQualifiers() )
.build();
}
else if ( !mapping.getJavaExpression().isEmpty() ) {
// its an expression
PropertyMapping.JavaExpressionMappingBuilder builder =
new PropertyMapping.JavaExpressionMappingBuilder();
propertyMapping = builder
.mappingContext( ctx )
.souceMethod( method )
.javaExpression( mapping.getJavaExpression() )
.targetAccessor( targetAccessor )
.build();
}
}
}
if ( propertyMapping == null ) {
for ( Parameter sourceParameter : method.getSourceParameters() ) {
PropertyMapping.PropertyMappingBuilder builder = new PropertyMapping.PropertyMappingBuilder();
PropertyMapping newPropertyMapping = builder
.mappingContext( ctx )
.souceMethod( method )
.targetAccessor( targetAccessor )
.targetPropertyName( targetPropertyName )
.parameter( sourceParameter )
.build();
if ( propertyMapping != null && newPropertyMapping != null ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"Several possible source properties for target property \"" + targetPropertyName +
"\".",
method.getExecutable()
);
break;
}
else if ( newPropertyMapping != null ) {
propertyMapping = newPropertyMapping;
}
}
}
if ( propertyMapping != null ) {
propertyMappings.add( propertyMapping );
mappedTargetProperties.add( targetPropertyName );
}
}
Set<String> targetProperties = Executables.getPropertyNames( targetAccessors );
reportErrorForUnmappedTargetPropertiesIfRequired(
method,
unmappedTargetPolicy,
targetProperties,
mappedTargetProperties,
ignoredTargetProperties
);
FactoryMethod factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
return new BeanMappingMethod( method, propertyMappings, factoryMethod );
}
/**
* Returns the effective policy for reporting unmapped getReturnType properties. If explicitly set via
* {@code Mapper}, this value will be returned. Otherwise the value from the corresponding processor option will
* be returned. If that is not set either, the default value from {@code Mapper#unmappedTargetPolicy()} will be
* returned.
*
* @param element The type declaring the generated mapper type
*
* @return The effective policy for reporting unmapped getReturnType properties.
*/
private ReportingPolicy getEffectiveUnmappedTargetPolicy() {
MapperConfig mapperSettings = MapperConfig.getInstanceOn( ctx.getMapperTypeElement() );
boolean setViaAnnotation = mapperSettings.isSetUnmappedTargetPolicy();
ReportingPolicy annotationValue = ReportingPolicy.valueOf( mapperSettings.unmappedTargetPolicy() );
if ( setViaAnnotation
|| ctx.getOptions().getUnmappedTargetPolicy() == null ) {
return annotationValue;
}
else {
return ctx.getOptions().getUnmappedTargetPolicy();
}
}
private CollectionMappingStrategy getEffectiveCollectionMappingStrategy() {
MapperConfig mapperSettings = MapperConfig.getInstanceOn( ctx.getMapperTypeElement() );
return mapperSettings.getCollectionMappingStrategy();
}
private boolean reportErrorIfMappedPropertiesDontExist() {
// only report errors if this method itself is configured
if ( method.isConfiguredByReverseMappingMethod() ) {
return true;
}
// collect all target accessors
List<ExecutableElement> targetAccessors = new ArrayList<ExecutableElement>();
targetAccessors.addAll( method.getResultType().getSetters() );
targetAccessors.addAll( method.getResultType().getAlternativeTargetAccessors() );
Set<String> targetProperties = Executables.getPropertyNames( targetAccessors );
boolean foundUnmappedProperty = false;
for ( List<Mapping> mappedProperties : method.getMappings().values() ) {
for ( Mapping mappedProperty : mappedProperties ) {
if ( mappedProperty.isIgnored() ) {
continue;
}
if ( mappedProperty.getSourceParameterName() != null ) {
Parameter sourceParameter = method.getSourceParameter(
mappedProperty.getSourceParameterName()
);
if ( sourceParameter == null ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Method has no parameter named \"%s\".",
mappedProperty.getSourceParameterName()
),
method.getExecutable(),
mappedProperty.getMirror(),
mappedProperty.getSourceAnnotationValue()
);
foundUnmappedProperty = true;
}
else {
if ( !hasSourceProperty( sourceParameter, mappedProperty.getSourcePropertyName() ) ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"The type of parameter \"%s\" has no property named \"%s\".",
mappedProperty.getSourceParameterName(),
mappedProperty.getSourcePropertyName()
),
method.getExecutable(),
mappedProperty.getMirror(),
mappedProperty.getSourceAnnotationValue()
);
foundUnmappedProperty = true;
}
}
}
else if ( mappedProperty.getConstant().isEmpty()
&& mappedProperty.getJavaExpression().isEmpty()
&& !hasSourceProperty( mappedProperty.getSourcePropertyName() ) ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"No property named \"%s\" exists in source parameter(s).",
mappedProperty.getSourceName()
),
method.getExecutable(),
mappedProperty.getMirror(),
mappedProperty.getSourceAnnotationValue()
);
foundUnmappedProperty = true;
}
if ( !targetProperties.contains( mappedProperty.getTargetName() ) ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Unknown property \"%s\" in return type %s.",
mappedProperty.getTargetName(),
method.getResultType()
),
method.getExecutable(),
mappedProperty.getMirror(),
mappedProperty.getTargetAnnotationValue()
);
foundUnmappedProperty = true;
}
}
}
return !foundUnmappedProperty;
}
private void reportErrorForUnmappedTargetPropertiesIfRequired( SourceMethod method,
ReportingPolicy unmappedTargetPolicy,
Set<String> targetProperties,
Set<String> mappedTargetProperties,
Set<String> ignoredTargetProperties ) {
Set<String> unmappedTargetProperties = new HashSet<String>();
for ( String property : targetProperties ) {
if ( !mappedTargetProperties.contains( property ) && !ignoredTargetProperties.contains( property ) ) {
unmappedTargetProperties.add( property );
}
}
if ( !unmappedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) {
ctx.getMessager().printMessage(
unmappedTargetPolicy.getDiagnosticKind(),
MessageFormat.format(
"Unmapped target {0,choice,1#property|1<properties}: \"{1}\"",
unmappedTargetProperties.size(),
Strings.join( unmappedTargetProperties, ", " )
),
method.getExecutable()
);
}
}
private boolean hasSourceProperty( String propertyName ) {
for ( Parameter parameter : method.getSourceParameters() ) {
if ( hasSourceProperty( parameter, propertyName ) ) {
return true;
}
}
return false;
}
private boolean hasSourceProperty( Parameter parameter, String propertyName ) {
List<ExecutableElement> getters = parameter.getType().getGetters();
return Executables.getPropertyNames( getters ).contains( propertyName );
}
}
private BeanMappingMethod(SourceMethod method,
List<PropertyMapping> propertyMappings,
FactoryMethod factoryMethod) {
super( method );

View File

@ -16,12 +16,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.model.assignment;
package org.mapstruct.ap.model;
import org.mapstruct.ap.model.assignment.Assignment;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.ModelElement;
import org.mapstruct.ap.model.common.Type;

View File

@ -18,11 +18,18 @@
*/
package org.mapstruct.ap.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.tools.Diagnostic;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.source.EnumMapping;
import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.util.Strings;
/**
* A {@link MappingMethod} which maps one enum type to another, optionally configured by one or more
@ -34,7 +41,165 @@ public class EnumMappingMethod extends MappingMethod {
private final List<EnumMapping> enumMappings;
public EnumMappingMethod(Method method, List<EnumMapping> enumMappings) {
public static class Builder {
private SourceMethod method;
private MappingContext ctx;
public Builder mappingContext( MappingContext mappingContext ) {
this.ctx = mappingContext;
return this;
}
public Builder souceMethod( SourceMethod sourceMethod ) {
this.method = sourceMethod;
return this;
}
public EnumMappingMethod build() {
if ( !reportErrorIfMappedEnumConstantsDontExist( method )
|| !reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( method ) ) {
return null;
}
List<EnumMapping> enumMappings = new ArrayList<EnumMapping>();
List<String> sourceEnumConstants
= method.getSourceParameters().iterator().next().getType().getEnumConstants();
Map<String, List<Mapping>> mappings = method.getMappings();
for ( String enumConstant : sourceEnumConstants ) {
List<Mapping> mappedConstants = mappings.get( enumConstant );
if ( mappedConstants == null ) {
enumMappings.add( new EnumMapping( enumConstant, enumConstant ) );
}
else if ( mappedConstants.size() == 1 ) {
enumMappings.add( new EnumMapping(
enumConstant, mappedConstants.iterator().next().getTargetName() ) );
}
else {
List<String> targetConstants = new ArrayList<String>( mappedConstants.size() );
for ( Mapping mapping : mappedConstants ) {
targetConstants.add( mapping.getTargetName() );
}
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"One enum constant must not be mapped to more than one target constant, "
+ "but constant %s is mapped to %s.",
enumConstant,
Strings.join( targetConstants, ", " )
),
method.getExecutable()
);
}
}
return new EnumMappingMethod( method, enumMappings );
}
private boolean reportErrorIfMappedEnumConstantsDontExist( SourceMethod method ) {
// only report errors if this method itself is configured
if ( method.isConfiguredByReverseMappingMethod() ) {
return true;
}
List<String> sourceEnumConstants =
method.getSourceParameters().iterator().next().getType().getEnumConstants();
List<String> targetEnumConstants = method.getReturnType().getEnumConstants();
boolean foundIncorrectMapping = false;
for ( List<Mapping> mappedConstants : method.getMappings().values() ) {
for ( Mapping mappedConstant : mappedConstants ) {
if ( mappedConstant.getSourceName() == null ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"A source constant must be specified for mappings of an enum mapping method.",
method.getExecutable(),
mappedConstant.getMirror()
);
foundIncorrectMapping = true;
}
else if ( !sourceEnumConstants.contains( mappedConstant.getSourceName() ) ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Constant %s doesn't exist in enum type %s.",
mappedConstant.getSourceName(),
method.getSourceParameters().iterator().next().getType()
),
method.getExecutable(),
mappedConstant.getMirror(),
mappedConstant.getSourceAnnotationValue()
);
foundIncorrectMapping = true;
}
if ( mappedConstant.getTargetName() == null ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"A target constant must be specified for mappings of an enum mapping method.",
method.getExecutable(),
mappedConstant.getMirror()
);
foundIncorrectMapping = true;
}
else if ( !targetEnumConstants.contains( mappedConstant.getTargetName() ) ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Constant %s doesn't exist in enum type %s.",
mappedConstant.getTargetName(),
method.getReturnType()
),
method.getExecutable(),
mappedConstant.getMirror(),
mappedConstant.getTargetAnnotationValue()
);
foundIncorrectMapping = true;
}
}
}
return !foundIncorrectMapping;
}
private boolean reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped(
SourceMethod method ) {
List<String> sourceEnumConstants =
method.getSourceParameters().iterator().next().getType().getEnumConstants();
List<String> targetEnumConstants = method.getReturnType().getEnumConstants();
Set<String> mappedSourceEnumConstants = method.getMappings().keySet();
List<String> unmappedSourceEnumConstants = new ArrayList<String>();
for ( String sourceEnumConstant : sourceEnumConstants ) {
if ( !targetEnumConstants.contains( sourceEnumConstant )
&& !mappedSourceEnumConstants.contains( sourceEnumConstant ) ) {
unmappedSourceEnumConstants.add( sourceEnumConstant );
}
}
if ( !unmappedSourceEnumConstants.isEmpty() ) {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"The following constants from the source enum have no corresponding constant in the "
+ "target enum and must be be mapped via @Mapping: %s",
Strings.join( unmappedSourceEnumConstants, ", " )
),
method.getExecutable()
);
}
return unmappedSourceEnumConstants.isEmpty();
}
}
private EnumMappingMethod( Method method, List<EnumMapping> enumMappings ) {
super( method );
this.enumMappings = enumMappings;
}

View File

@ -18,8 +18,12 @@
*/
package org.mapstruct.ap.model;
import org.mapstruct.ap.model.assignment.Assignment;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.assignment.TypeConversion;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import org.mapstruct.ap.model.assignment.SetterWrapper;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
@ -37,7 +41,71 @@ public class IterableMappingMethod extends MappingMethod {
private final FactoryMethod factoryMethod;
private final boolean overridden;
public IterableMappingMethod(Method method, Assignment parameterAssignment, FactoryMethod factoryMethod) {
public static class Builder {
private Method method;
private MappingContext ctx;
private String dateFormat;
private List<TypeMirror> qualifiers;
public Builder mappingContext( MappingContext mappingContext ) {
this.ctx = mappingContext;
return this;
}
public Builder method( Method sourceMethod ) {
this.method = sourceMethod;
return this;
}
public Builder dateFormat( String dateFormat ) {
this.dateFormat = dateFormat;
return this;
}
public Builder qualifiers( List<TypeMirror> qualifiers ) {
this.qualifiers = qualifiers;
return this;
}
public IterableMappingMethod build( ) {
Type sourceElementType =
method.getSourceParameters().iterator().next().getType().getTypeParameters().get( 0 );
Type targetElementType =
method.getResultType().getTypeParameters().get( 0 );
String conversionStr =
Strings.getSaveVariableName( sourceElementType.getName(), method.getParameterNames() );
Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method,
"collection element",
sourceElementType,
targetElementType,
null, // there is no targetPropertyName
dateFormat,
qualifiers,
conversionStr
);
if ( assignment == null ) {
String message = String.format(
"Can't create implementation of method %s. Found no method nor built-in conversion for mapping "
+ "source element type into target element type.",
method
);
method.printMessage( ctx.getMessager(), Diagnostic.Kind.ERROR, message );
}
// target accessor is setter, so decorate assignment as setter
assignment = new SetterWrapper( assignment, method.getThrownTypes() );
FactoryMethod factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
return new IterableMappingMethod( method, assignment, factoryMethod );
}
}
private IterableMappingMethod(Method method, Assignment parameterAssignment, FactoryMethod factoryMethod) {
super( method );
this.elementAssignment = parameterAssignment;
this.factoryMethod = factoryMethod;

View File

@ -18,8 +18,12 @@
*/
package org.mapstruct.ap.model;
import org.mapstruct.ap.model.assignment.Assignment;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.assignment.TypeConversion;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import org.mapstruct.ap.model.assignment.LocalVarWrapper;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
@ -38,7 +42,101 @@ public class MapMappingMethod extends MappingMethod {
private final FactoryMethod factoryMethod;
private final boolean overridden;
public MapMappingMethod(Method method, Assignment keyAssignment, Assignment valueAssignment,
public static class Builder {
private String keyDateFormat;
private String valueDateFormat;
private List<TypeMirror> keyQualifiers;
private List<TypeMirror> valueQualifiers;
private Method method;
private MappingContext ctx;
public Builder mappingContext( MappingContext mappingContext ) {
this.ctx = mappingContext;
return this;
}
public Builder method( Method sourceMethod ) {
this.method = sourceMethod;
return this;
}
public Builder keyDateFormat( String keyDateFormat ) {
this.keyDateFormat = keyDateFormat;
return this;
}
public Builder valueDateFormat( String valueDateFormat ) {
this.valueDateFormat = valueDateFormat;
return this;
}
public Builder keyQualifiers( List<TypeMirror> keyQualifiers ) {
this.keyQualifiers = keyQualifiers;
return this;
}
public Builder valueQualifiers( List<TypeMirror> valueQualifiers ) {
this.valueQualifiers = valueQualifiers;
return this;
}
public MapMappingMethod build() {
List<Type> sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters();
List<Type> resultTypeParams = method.getResultType().getTypeParameters();
// find mapping method or conversion for key
Type keySourceType = sourceTypeParams.get( 0 );
Type keyTargetType = resultTypeParams.get( 0 );
Assignment keyAssignment = ctx.getMappingResolver().getTargetAssignment( method,
"map key",
keySourceType,
keyTargetType,
null, // there is no targetPropertyName
keyDateFormat,
keyQualifiers,
"entry.getKey()"
);
if ( keyAssignment == null ) {
String message = String.format( "Can't create implementation of method %s. Found no method nor "
+ "built-in conversion for mapping source key type to target key type.", method );
method.printMessage( ctx.getMessager(), Diagnostic.Kind.ERROR, message );
}
// find mapping method or conversion for value
Type valueSourceType = sourceTypeParams.get( 1 );
Type valueTargetType = resultTypeParams.get( 1 );
Assignment valueAssignment = ctx.getMappingResolver().getTargetAssignment( method,
"map value",
valueSourceType,
valueTargetType,
null, // there is no targetPropertyName
valueDateFormat,
valueQualifiers,
"entry.getValue()"
);
if ( valueAssignment == null ) {
String message = String.format( "Can't create implementation of method %s. Found no method nor "
+ "built-in conversion for mapping source value type to target value type.", method );
method.printMessage( ctx.getMessager(), Diagnostic.Kind.ERROR, message );
}
FactoryMethod factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes() );
valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes() );
return new MapMappingMethod( method, keyAssignment, valueAssignment, factoryMethod );
}
}
private MapMappingMethod(Method method, Assignment keyAssignment, Assignment valueAssignment,
FactoryMethod factoryMethod) {
super( method );

View File

@ -0,0 +1,171 @@
/**
* 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 java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.Messager;
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.common.TypeFactory;
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.
*
* <p>
* The following mappers make use of this context:
* <ul>
* <li>{@link BeanMappingMethod.Builder}</li>
* <li>{@link PropertyMappingMethod.Builder}</li>
* <li>{@link IterableMappingMethod.Builder}</li>
* <li>{@link MapMappingMethod.Builder}</li>
* <li>{@link EnumMappingMethod.Builder}</li>
* </ul>
* </p>
* <p>
* The context provides:
* <ul>
* <li>Input for the building process, such as the source model (mapping methods found) and mapper references.</li>
* <li>Required factory, utility, reporting methods for building the mappings.</li>
* <li>Means to harbor results produced by the builders, such as forged- and virtual mapping methods that should be
* generated in a later stage.</li>
* </ul>
* </p>
*
* @author Sjaak Derksen
*/
public class MappingContext {
private final TypeFactory typeFactory;
private final Elements elementUtils;
private final Types typeUtils;
private final Messager messager;
private final Options options;
private final TypeElement mapperTypeElement;
private final List<SourceMethod> sourceModel;
private final List<MapperReference> mapperReferences;
private 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,
Elements elementUtils,
Types typeUtils,
Messager messager,
Options options,
TypeElement mapper,
List<SourceMethod> sourceModel ) {
this.typeFactory = typeFactory;
this.elementUtils = elementUtils;
this.typeUtils = typeUtils;
this.messager = messager;
this.options = options;
this.mapperTypeElement = mapper;
this.sourceModel = sourceModel;
this.mapperReferences = initReferencedMappers( mapper );
}
public TypeElement getMapperTypeElement() {
return mapperTypeElement;
}
public List<SourceMethod> getSourceModel() {
return sourceModel;
}
public List<MapperReference> getMapperReferences() {
return mapperReferences;
}
public TypeFactory getTypeFactory() {
return typeFactory;
}
public Elements getElementUtils() {
return elementUtils;
}
public Types getTypeUtils() {
return typeUtils;
}
public Messager getMessager() {
return messager;
}
public Options getOptions() {
return options;
}
public MappingResolver getMappingResolver() {
return mappingResolver;
}
public void setMappingResolver(MappingResolver mappingResolver) {
this.mappingResolver = mappingResolver;
}
public List<MappingMethod> getMappingsToGenerate() {
return mappingsToGenerate;
}
public Set<VirtualMappingMethod> getUsedVirtualMappings() {
return usedVirtualMappings;
}
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

@ -0,0 +1,63 @@
/**
* 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

@ -16,17 +16,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.model.assignment;
package org.mapstruct.ap.model;
import org.mapstruct.ap.model.assignment.Assignment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.FactoryMethod;
import org.mapstruct.ap.model.MapperReference;
import org.mapstruct.ap.model.MappingMethod;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;

View File

@ -18,10 +18,30 @@
*/
package org.mapstruct.ap.model;
import org.mapstruct.ap.model.assignment.Assignment;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED;
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED;
import org.mapstruct.ap.model.assignment.AdderWrapper;
import org.mapstruct.ap.model.assignment.GetterCollectionOrMapWrapper;
import org.mapstruct.ap.model.assignment.NewCollectionOrMapWrapper;
import org.mapstruct.ap.model.assignment.NullCheckWrapper;
import org.mapstruct.ap.model.assignment.SetterCollectionOrMapWrapper;
import org.mapstruct.ap.model.assignment.SetterWrapper;
import org.mapstruct.ap.model.common.ModelElement;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.ForgedMethod;
import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.util.Executables;
/**
@ -41,12 +61,414 @@ public class PropertyMapping extends ModelElement {
private final Assignment assignment;
public static class PropertyMappingBuilder {
private MappingContext ctx;
private SourceMethod method;
private ExecutableElement targetAccessor;
private String targetPropertyName;
private Parameter parameter;
public PropertyMappingBuilder mappingContext(MappingContext mappingContext) {
this.ctx = mappingContext;
return this;
}
public PropertyMappingBuilder souceMethod( SourceMethod sourceMethod ) {
this.method = sourceMethod;
return this;
}
public PropertyMappingBuilder targetAccessor( ExecutableElement targetAccessor ) {
this.targetAccessor = targetAccessor;
return this;
}
public PropertyMappingBuilder targetPropertyName( String targetPropertyName ) {
this.targetPropertyName = targetPropertyName;
return this;
}
public PropertyMappingBuilder parameter( Parameter parameter ) {
this.parameter = parameter;
return this;
}
private enum TargetAccessorType {
GETTER,
SETTER,
ADDER
}
public PropertyMapping build() {
// check if there's a mapping defined
Mapping mapping = method.getMappingByTargetPropertyName( targetPropertyName );
String dateFormat = null;
List<TypeMirror> qualifiers = null;
String sourcePropertyName;
if ( mapping != null ) {
dateFormat = mapping.getDateFormat();
qualifiers = mapping.getQualifiers();
sourcePropertyName = mapping.getSourcePropertyName();
}
else {
sourcePropertyName = targetPropertyName;
}
List<ExecutableElement> sourceGetters = parameter.getType().getGetters();
// then iterate over source accessors (assuming the source is a bean)
for ( ExecutableElement sourceAccessor : sourceGetters ) {
List<Mapping> sourceMappings = method.getMappings().get( sourcePropertyName );
if ( method.getMappings().containsKey( sourcePropertyName ) ) {
for ( Mapping sourceMapping : sourceMappings ) {
boolean mapsToOtherTarget = !sourceMapping.getTargetName().equals( targetPropertyName );
if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName )
&& !mapsToOtherTarget ) {
return getPropertyMapping( sourceAccessor, dateFormat, qualifiers );
}
}
}
else if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName ) ) {
return getPropertyMapping( sourceAccessor, dateFormat, qualifiers );
}
}
return null;
}
private PropertyMapping getPropertyMapping( ExecutableElement sourceAccessor,
String dateFormat,
List<TypeMirror> qualifiers ) {
Type sourceType;
Type targetType;
TargetAccessorType targetAccessorType;
String sourceReference = parameter.getName() + "." + sourceAccessor.getSimpleName().toString() + "()";
String iteratorReference = null;
boolean sourceIsCollection = false;
if ( Executables.isSetterMethod( targetAccessor ) ) {
sourceType = ctx.getTypeFactory().getReturnType( sourceAccessor );
targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType();
targetAccessorType = TargetAccessorType.SETTER;
}
else if ( Executables.isAdderMethod( targetAccessor ) ) {
sourceType = ctx.getTypeFactory().getReturnType( sourceAccessor );
if ( sourceType.isCollectionType() ) {
sourceIsCollection = true;
sourceType = sourceType.getTypeParameters().get( 0 );
iteratorReference = Executables.getElementNameForAdder( targetAccessor );
}
targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType();
targetAccessorType = TargetAccessorType.ADDER;
}
else {
sourceType = ctx.getTypeFactory().getReturnType( sourceAccessor );
targetType = ctx.getTypeFactory().getReturnType( targetAccessor );
targetAccessorType = TargetAccessorType.GETTER;
}
String sourcePropertyName = Executables.getPropertyName( sourceAccessor );
String mappedElement = "property '" + sourcePropertyName + "'";
Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method,
mappedElement,
sourceType,
targetType,
targetPropertyName,
dateFormat,
qualifiers,
iteratorReference != null ? iteratorReference : sourceReference
);
if ( assignment == null ) {
assignment = forgeMapping( sourceType, targetType, sourceReference, method.getExecutable() );
}
if ( assignment != null ) {
if ( targetType.isCollectionOrMapType() ) {
// wrap the setter in the collection / map initializers
if ( targetAccessorType == TargetAccessorType.SETTER ) {
// wrap the assignment in a new Map or Collection implementation if this is not done in a
// mapping method. Note, typeconversons do not apply to collections or maps
Assignment newCollectionOrMap = null;
if ( assignment.getType() == DIRECT ) {
newCollectionOrMap =
new NewCollectionOrMapWrapper( assignment, targetType.getImportTypes() );
newCollectionOrMap = new SetterWrapper( newCollectionOrMap, method.getThrownTypes() );
}
// wrap the assignment in the setter method
assignment = new SetterWrapper( assignment, method.getThrownTypes() );
// target accessor is setter, so wrap the setter in setter map/ collection handling
assignment = new SetterCollectionOrMapWrapper(
assignment,
targetAccessor.getSimpleName().toString(),
newCollectionOrMap
);
}
else {
// wrap the assignment in the setter method
assignment = new SetterWrapper( assignment, method.getThrownTypes() );
// target accessor is getter, so wrap the setter in getter map/ collection handling
assignment = new GetterCollectionOrMapWrapper( assignment );
}
// For collections and maps include a null check, when the assignment type is DIRECT.
// for mapping methods (builtin / custom), the mapping method is responsible for the
// null check. Typeconversions do not apply to collections and maps.
if ( assignment.getType() == DIRECT ) {
assignment = new NullCheckWrapper( assignment );
}
}
else {
if ( targetAccessorType == TargetAccessorType.SETTER ) {
assignment = new SetterWrapper( assignment, method.getThrownTypes() );
if ( !sourceType.isPrimitive()
&& ( assignment.getType() == TYPE_CONVERTED
|| assignment.getType() == TYPE_CONVERTED_MAPPED
|| assignment.getType() == DIRECT && targetType.isPrimitive() ) ) {
// for primitive types null check is not possible at all, but a conversion needs
// a null check.
assignment = new NullCheckWrapper( assignment );
}
}
else {
// TargetAccessorType must be ADDER
if ( sourceIsCollection ) {
assignment = new AdderWrapper(
assignment,
method.getThrownTypes(),
sourceReference,
sourceType
);
}
else {
// Possibly adding null to a target collection. So should be surrounded by an null check.
assignment = new SetterWrapper( assignment, method.getThrownTypes() );
assignment = new NullCheckWrapper( assignment );
}
}
}
}
else {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Can't map property \"%s %s\" to \"%s %s\".",
sourceType,
sourcePropertyName,
targetType,
targetPropertyName
),
method.getExecutable()
);
}
return new PropertyMapping(
parameter.getName(),
targetAccessor.getSimpleName().toString(),
targetType,
assignment
);
}
private Assignment forgeMapping( Type sourceType, Type targetType, String sourceReference, Element element ) {
Assignment assignment = null;
if ( sourceType.isCollectionType() && targetType.isCollectionType() ) {
ForgedMethod methodToGenerate = new ForgedMethod( sourceType, targetType, element );
IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder( );
IterableMappingMethod iterableMappingMethod = builder
.mappingContext( ctx )
.method( methodToGenerate )
.build();
if ( !ctx.getMappingsToGenerate().contains( iterableMappingMethod ) ) {
ctx.getMappingsToGenerate().add( iterableMappingMethod );
}
assignment = AssignmentFactory.createMethodReference( methodToGenerate, null, targetType );
assignment.setAssignment( AssignmentFactory.createSimple( sourceReference ) );
}
else if ( sourceType.isMapType() && targetType.isMapType() ) {
ForgedMethod methodToGenerate = new ForgedMethod( sourceType, targetType, element );
MapMappingMethod.Builder builder = new MapMappingMethod.Builder( );
MapMappingMethod mapMappingMethod = builder
.mappingContext( ctx )
.method( methodToGenerate )
.build();
if ( !ctx.getMappingsToGenerate().contains( mapMappingMethod ) ) {
ctx.getMappingsToGenerate().add( mapMappingMethod );
}
assignment = AssignmentFactory.createMethodReference( methodToGenerate, null, targetType );
assignment.setAssignment( AssignmentFactory.createSimple( sourceReference ) );
}
return assignment;
}
}
public static class ConstantMappingBuilder {
private MappingContext ctx;
private SourceMethod method;
private String constantExpression;
private ExecutableElement targetAccessor;
private String dateFormat;
private List<TypeMirror> qualifiers;
public ConstantMappingBuilder mappingContext(MappingContext mappingContext) {
this.ctx = mappingContext;
return this;
}
public ConstantMappingBuilder sourceMethod( SourceMethod sourceMethod ) {
this.method = sourceMethod;
return this;
}
public ConstantMappingBuilder constantExpression( String constantExpression ) {
this.constantExpression = constantExpression;
return this;
}
public ConstantMappingBuilder targetAccessor( ExecutableElement targetAccessor ) {
this.targetAccessor = targetAccessor;
return this;
}
public ConstantMappingBuilder dateFormat( String dateFormat ) {
this.dateFormat = dateFormat;
return this;
}
public ConstantMappingBuilder qualifiers( List<TypeMirror> qualifiers ) {
this.qualifiers = qualifiers;
return this;
}
public PropertyMapping build() {
// source
String mappedElement = "constant '" + constantExpression + "'";
Type sourceType = ctx.getTypeFactory().getType( String.class );
// target
Type targetType;
if ( Executables.isSetterMethod( targetAccessor ) ) {
targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType();
}
else {
targetType = ctx.getTypeFactory().getReturnType( targetAccessor );
}
String targetPropertyName = Executables.getPropertyName( targetAccessor );
Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method,
mappedElement,
sourceType,
targetType,
targetPropertyName,
dateFormat,
qualifiers,
constantExpression
);
if ( assignment != null ) {
// target accessor is setter, so decorate assignment as setter
assignment = new SetterWrapper( assignment, method.getThrownTypes() );
// wrap when dealing with getter only on target
if ( Executables.isGetterMethod( targetAccessor ) ) {
assignment = new GetterCollectionOrMapWrapper( assignment );
}
}
else {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Can't map \"%s %s\" to \"%s %s\".",
sourceType,
constantExpression,
targetType,
targetPropertyName
),
method.getExecutable()
);
}
return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment );
}
}
public static class JavaExpressionMappingBuilder {
private MappingContext ctx;
private SourceMethod method;
private String javaExpression;
private ExecutableElement targetAccessor;
public JavaExpressionMappingBuilder mappingContext(MappingContext mappingContext) {
this.ctx = mappingContext;
return this;
}
public JavaExpressionMappingBuilder souceMethod( SourceMethod sourceMethod ) {
this.method = sourceMethod;
return this;
}
public JavaExpressionMappingBuilder javaExpression( String javaExpression ) {
this.javaExpression = javaExpression;
return this;
}
public JavaExpressionMappingBuilder targetAccessor( ExecutableElement targetAccessor ) {
this.targetAccessor = targetAccessor;
return this;
}
public PropertyMapping build() {
Assignment assignment = AssignmentFactory.createSimple( javaExpression );
assignment = new SetterWrapper( assignment, method.getThrownTypes() );
Type targetType;
if ( Executables.isSetterMethod( targetAccessor ) ) {
targetType = ctx.getTypeFactory().getSingleParameter( targetAccessor ).getType();
}
else {
targetType = ctx.getTypeFactory().getReturnType( targetAccessor );
// target accessor is getter, so wrap the setter in getter map/ collection handling
assignment = new GetterCollectionOrMapWrapper( assignment );
}
return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment );
}
}
// Constructor for creating mappings of constant expressions.
public PropertyMapping(String targetAccessorName, Type targetType, Assignment propertyAssignment) {
private PropertyMapping(String targetAccessorName, Type targetType, Assignment propertyAssignment) {
this( null, targetAccessorName, targetType, propertyAssignment );
}
public PropertyMapping(String sourceBeanName, String targetAccessorName, Type targetType, Assignment assignment) {
private PropertyMapping(String sourceBeanName, String targetAccessorName, Type targetType, Assignment assignment) {
this.sourceBeanName = sourceBeanName;

View File

@ -16,13 +16,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.model.assignment;
package org.mapstruct.ap.model;
import org.mapstruct.ap.model.assignment.Assignment;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.ModelElement;
import org.mapstruct.ap.model.common.Type;

View File

@ -23,7 +23,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.Type;
/**

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.model;
package org.mapstruct.ap.model.assignment;
import java.util.List;
import java.util.Set;

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.assignment;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.FactoryMethod;
import org.mapstruct.ap.model.MapperReference;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.builtin.BuiltInMethod;
/**
* Factory class for creating all types of assignments
*
* @author Sjaak Derksen
*/
public class AssignmentFactory {
private AssignmentFactory() {
}
public static Assignment createTypeConversion(Set<Type> importTypes, List<Type> exceptionTypes, String expression) {
return new TypeConversion( importTypes, exceptionTypes, expression );
}
public static FactoryMethod createFactory(SourceMethod method, MapperReference declaringMapper) {
return new MethodReference( method, declaringMapper, null );
}
public static Assignment createMethodReference(Method method, MapperReference declaringMapper,
Type targetType) {
return new MethodReference( method, declaringMapper, targetType );
}
public static Assignment createMethodReference(BuiltInMethod method, ConversionContext contextParam) {
return new MethodReference( method, contextParam );
}
public static Direct createSimple(String sourceRef) {
return new Direct( sourceRef );
}
}

View File

@ -20,7 +20,6 @@ package org.mapstruct.ap.model.assignment;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.ModelElement;
import org.mapstruct.ap.model.common.Type;

View File

@ -18,8 +18,6 @@
*/
package org.mapstruct.ap.model.assignment;
import org.mapstruct.ap.model.Assignment;
/**
* This wrapper handles the situation were an assignment must be done via a target getter method because there
* is no setter available.

View File

@ -20,7 +20,6 @@ package org.mapstruct.ap.model.assignment;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.Type;
/**

View File

@ -21,7 +21,6 @@ package org.mapstruct.ap.model.assignment;
import java.util.HashSet;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.Type;
/**

View File

@ -18,8 +18,6 @@
*/
package org.mapstruct.ap.model.assignment;
import org.mapstruct.ap.model.Assignment;
/**
* Wraps the assignment in a null check.
*

View File

@ -21,7 +21,6 @@ package org.mapstruct.ap.model.assignment;
import java.util.HashSet;
import java.util.Set;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.Type;
/**

View File

@ -20,7 +20,6 @@ package org.mapstruct.ap.model.assignment;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.ap.model.Assignment;
import org.mapstruct.ap.model.common.Type;
/**

View File

@ -18,28 +18,26 @@
*/
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;
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.assignment.AssignmentFactory;
import org.mapstruct.ap.model.assignment.Direct;
import org.mapstruct.ap.model.AssignmentFactory;
import org.mapstruct.ap.model.Direct;
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;
@ -67,30 +65,29 @@ import org.mapstruct.ap.util.Strings;
*
* @author Sjaak Derksen
*/
public class MappingResolver {
public class MappingResolverImpl implements MappingResolver {
private final Messager messager;
private final TypeFactory typeFactory;
private final Conversions conversions;
private final BuiltInMappingMethods builtInMethods;
private final Types typeUtils;
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.
types.
* @param mappingContext
*/
private final Set<VirtualMappingMethod> virtualMethods;
public MappingResolver(Messager messager, TypeFactory typeFactory, Elements elementUtils, Types typeUtils) {
this.messager = messager;
this.typeFactory = typeFactory;
this.conversions = new Conversions( elementUtils, typeFactory );
this.builtInMethods = new BuiltInMappingMethods( typeFactory );
this.virtualMethods = new HashSet<VirtualMappingMethod>();
this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, typeFactory );
this.typeUtils = typeUtils;
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;
}
@ -99,8 +96,6 @@ public class MappingResolver {
*
* @param mappingMethod target mapping method
* @param mappedElement used for error messages
* @param mapperReferences list of references to mapper
* @param methods list of candidate methods
* @param sourceType parameter to match
* @param targetType return type to match
* @param targetPropertyName name of the target property
@ -116,10 +111,10 @@ public class MappingResolver {
* <li>null, no assignment found</li>
* </ol>
*/
public Assignment getTargetAssignment( Method mappingMethod,
@Override
public Assignment getTargetAssignment(
Method mappingMethod,
String mappedElement,
List<MapperReference> mapperReferences,
List<SourceMethod> methods,
Type sourceType,
Type targetType,
String targetPropertyName,
@ -129,22 +124,19 @@ public class MappingResolver {
ResolvingAttempt attempt = new ResolvingAttempt( mappingMethod,
mappedElement,
mapperReferences,
methods,
targetPropertyName,
dateFormat,
qualifiers,
sourceReference,
this
conversions,
builtInMethods,
methodSelectors,
mappingContext
);
return attempt.getTargetAssignment( sourceType, targetType );
}
public Set<VirtualMappingMethod> getVirtualMethodsToGenerate() {
return virtualMethods;
}
private static class ResolvingAttempt {
@ -156,7 +148,10 @@ public class MappingResolver {
private final String dateFormat;
private final List<TypeMirror> qualifiers;
private final String sourceReference;
private final MappingResolver context;
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
@ -165,22 +160,26 @@ public class MappingResolver {
private ResolvingAttempt( Method mappingMethod,
String mappedElement,
List<MapperReference> mapperReferences,
List<SourceMethod> methods,
String targetPropertyName,
String dateFormat,
List<TypeMirror> qualifiers,
String sourceReference,
MappingResolver context ) {
Conversions conversions,
BuiltInMappingMethods builtInMethods,
MethodSelectors methodSelectors,
MappingContext mappingContext ) {
this.mappingMethod = mappingMethod;
this.mappedElement = mappedElement;
this.mapperReferences = mapperReferences;
this.methods = methods;
this.mapperReferences = mappingContext.getMapperReferences();
this.methods = mappingContext.getSourceModel();
this.targetPropertyName = targetPropertyName;
this.dateFormat = dateFormat;
this.qualifiers = qualifiers;
this.sourceReference = sourceReference;
this.context = context;
this.conversions = conversions;
this.builtInMethods = builtInMethods;
this.methodSelectors = methodSelectors;
this.mappingContext = mappingContext;
this.virtualMethodCandidates = new HashSet<VirtualMappingMethod>();
}
@ -190,12 +189,12 @@ public class MappingResolver {
Assignment referencedMethod = resolveViaMethod( sourceType, targetType );
if ( referencedMethod != null ) {
referencedMethod.setAssignment( AssignmentFactory.createSimple( sourceReference ) );
context.virtualMethods.addAll( virtualMethodCandidates );
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
return referencedMethod;
}
// then direct assignable
if ( sourceType.isAssignableTo( targetType ) || context.isPropertyMappable( sourceType, targetType ) ) {
if ( sourceType.isAssignableTo( targetType ) || isPropertyMappable( sourceType, targetType ) ) {
Assignment simpleAssignment = AssignmentFactory.createSimple( sourceReference );
return simpleAssignment;
}
@ -210,21 +209,21 @@ public class MappingResolver {
// 2 step method, first: method(method(souurce))
referencedMethod = resolveViaMethodAndMethod( sourceType, targetType );
if ( referencedMethod != null ) {
context.virtualMethods.addAll( virtualMethodCandidates );
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
return referencedMethod;
}
// 2 step method, then: method(conversion(souurce))
referencedMethod = resolveViaConversionAndMethod( sourceType, targetType );
if ( referencedMethod != null ) {
context.virtualMethods.addAll( virtualMethodCandidates );
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
return referencedMethod;
}
// 2 step method, finally: conversion(method(souurce))
conversion = resolveViaMethodAndConversion( sourceType, targetType );
if ( conversion != null ) {
context.virtualMethods.addAll( virtualMethodCandidates );
mappingContext.getUsedVirtualMappings().addAll( virtualMethodCandidates );
return conversion;
}
@ -233,13 +232,14 @@ public class MappingResolver {
}
private Assignment resolveViaConversion( Type sourceType, Type targetType ) {
ConversionProvider conversionProvider = context.conversions.getConversion( sourceType, targetType );
ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType );
if ( conversionProvider == null ) {
return null;
}
ConversionContext ctx = new DefaultConversionContext( context.typeFactory, targetType, dateFormat );
ConversionContext ctx =
new DefaultConversionContext( mappingContext.getTypeFactory(), targetType, dateFormat );
return conversionProvider.to( ctx );
}
@ -259,11 +259,12 @@ public class MappingResolver {
// then a matching built-in method
BuiltInMethod matchingBuiltInMethod =
getBestMatch( context.builtInMethods.getBuiltInMethods(), sourceType, targetType );
getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType );
if ( matchingBuiltInMethod != null ) {
virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod ) );
ConversionContext ctx = new DefaultConversionContext( context.typeFactory, targetType, dateFormat );
ConversionContext ctx =
new DefaultConversionContext( mappingContext.getTypeFactory(), targetType, dateFormat );
Assignment methodReference = AssignmentFactory.createMethodReference( matchingBuiltInMethod, ctx );
methodReference.setAssignment( AssignmentFactory.createSimple( sourceReference ) );
return methodReference;
@ -285,7 +286,7 @@ public class MappingResolver {
private Assignment resolveViaMethodAndMethod( Type sourceType, Type targetType ) {
List<Method> methodYCandidates = new ArrayList<Method>( methods );
methodYCandidates.addAll( context.builtInMethods.getBuiltInMethods() );
methodYCandidates.addAll( builtInMethods.getBuiltInMethods() );
Assignment methodRefY = null;
@ -331,7 +332,7 @@ public class MappingResolver {
private Assignment resolveViaConversionAndMethod( Type sourceType, Type targetType ) {
List<Method> methodYCandidates = new ArrayList<Method>( methods );
methodYCandidates.addAll( context.builtInMethods.getBuiltInMethods() );
methodYCandidates.addAll( builtInMethods.getBuiltInMethods() );
Assignment methodRefY = null;
@ -373,7 +374,7 @@ public class MappingResolver {
private Assignment resolveViaMethodAndConversion( Type sourceType, Type targetType ) {
List<Method> methodXCandidates = new ArrayList<Method>( methods );
methodXCandidates.addAll( context.builtInMethods.getBuiltInMethods() );
methodXCandidates.addAll( builtInMethods.getBuiltInMethods() );
Assignment conversionYRef = null;
@ -404,7 +405,7 @@ public class MappingResolver {
private <T extends Method> T getBestMatch( List<T> methods, Type sourceType, Type returnType ) {
List<T> candidates = context.methodSelectors.getMatchingMethods(
List<T> candidates = methodSelectors.getMatchingMethods(
mappingMethod,
methods,
sourceType,
@ -423,7 +424,7 @@ public class MappingResolver {
returnType,
Strings.join( candidates, ", " ) );
mappingMethod.printMessage( context.messager, Kind.ERROR, errorMsg );
mappingMethod.printMessage( mappingContext.getMessager(), Kind.ERROR, errorMsg );
}
if ( !candidates.isEmpty() ) {
@ -453,114 +454,115 @@ public class MappingResolver {
}
return null;
}
}
/**
* Whether the specified property can be mapped from source to target or not. A mapping if possible if one of
* the following conditions is true:
* <ul>
* <li>the source type is assignable to the target type</li>
* <li>a mapping method exists</li>
* <li>a built-in conversion exists</li>
* <li>the property is of a collection or map type and the constructor of the target type (either itself or its
* implementation type) accepts the source type.</li>
* </ul>
*
* @param property The property mapping to check.
*
* @return {@code true} if the specified property can be mapped, {@code false} otherwise.
*/
private boolean isPropertyMappable(Type sourceType, Type targetType) {
boolean collectionOrMapTargetTypeHasCompatibleConstructor = false;
/**
* Whether the specified property can be mapped from source to target or not. A mapping if possible if one of
* the following conditions is true:
* <ul>
* <li>the source type is assignable to the target type</li>
* <li>a mapping method exists</li>
* <li>a built-in conversion exists</li>
* <li>the property is of a collection or map type and the constructor of the target type (either itself or its
* implementation type) accepts the source type.</li>
* </ul>
*
* @param property The property mapping to check.
*
* @return {@code true} if the specified property can be mapped, {@code false} otherwise.
*/
private boolean isPropertyMappable( Type sourceType, Type targetType ) {
boolean collectionOrMapTargetTypeHasCompatibleConstructor = false;
if ( sourceType.isCollectionType() && targetType.isCollectionType() ) {
collectionOrMapTargetTypeHasCompatibleConstructor = collectionTypeHasCompatibleConstructor(
sourceType,
targetType.getImplementationType() != null ?
targetType.getImplementationType() : targetType
);
if ( sourceType.isCollectionType() && targetType.isCollectionType() ) {
collectionOrMapTargetTypeHasCompatibleConstructor = collectionTypeHasCompatibleConstructor(
sourceType,
targetType.getImplementationType() != null
? targetType.getImplementationType() : targetType
);
}
if ( sourceType.isMapType() && targetType.isMapType() ) {
collectionOrMapTargetTypeHasCompatibleConstructor = mapTypeHasCompatibleConstructor(
sourceType,
targetType.getImplementationType() != null
? targetType.getImplementationType() : targetType
);
}
if ( ( ( targetType.isCollectionType() || targetType.isMapType() )
&& collectionOrMapTargetTypeHasCompatibleConstructor ) ) {
return true;
}
return false;
}
if ( sourceType.isMapType() && targetType.isMapType() ) {
collectionOrMapTargetTypeHasCompatibleConstructor = mapTypeHasCompatibleConstructor(
sourceType,
targetType.getImplementationType() != null ?
targetType.getImplementationType() : targetType
);
/**
* Whether the given target type has a single-argument constructor which accepts the given source type.
*
* @param sourceType the source type
* @param targetType the target type
*
* @return {@code true} if the target type has a constructor accepting the given source type, {@code false}
* otherwise.
*/
private boolean collectionTypeHasCompatibleConstructor( Type sourceType, Type targetType ) {
// note (issue #127): actually this should check for the presence of a matching constructor, with help of
// Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead
// we just check whether the target type is parameterized in a way that it implicitly should have a
// constructor which accepts the source type
TypeMirror sourceElementType = sourceType.getTypeParameters().isEmpty()
? mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror()
: sourceType.getTypeParameters().get( 0 ).getTypeMirror();
TypeMirror targetElementType = targetType.getTypeParameters().isEmpty()
? mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror()
: targetType.getTypeParameters().get( 0 ).getTypeMirror();
return mappingContext.getTypeUtils().isAssignable( sourceElementType, targetElementType );
}
if ( ( ( targetType.isCollectionType() || targetType.isMapType() ) &&
collectionOrMapTargetTypeHasCompatibleConstructor ) ) {
return true;
/**
* Whether the given target type has a single-argument constructor which accepts the given source type.
*
* @param sourceType the source type
* @param targetType the target type
*
* @return {@code true} if the target type has a constructor accepting the given source type, {@code false}
* otherwise.
*/
private boolean mapTypeHasCompatibleConstructor( Type sourceType, Type targetType ) {
// note (issue #127): actually this should check for the presence of a matching constructor, with help of
// Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead
// we just check whether the target type is parameterized in a way that it implicitly should have a
// constructor which accepts the source type
TypeMirror sourceKeyType;
TypeMirror targetKeyType;
TypeMirror sourceValueType;
TypeMirror targetValueType;
if ( sourceType.getTypeParameters().isEmpty() ) {
sourceKeyType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror();
sourceValueType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror();
}
else {
sourceKeyType = sourceType.getTypeParameters().get( 0 ).getTypeMirror();
sourceValueType = sourceType.getTypeParameters().get( 1 ).getTypeMirror();
}
if ( targetType.getTypeParameters().isEmpty() ) {
targetKeyType = mappingContext.getTypeFactory().getType( Object.class ).getTypeMirror();
targetValueType = mappingContext.getTypeFactory().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 false;
}
/**
* Whether the given target type has a single-argument constructor which accepts the given source type.
*
* @param sourceType the source type
* @param targetType the target type
*
* @return {@code true} if the target type has a constructor accepting the given source type, {@code false}
* otherwise.
*/
private boolean collectionTypeHasCompatibleConstructor(Type sourceType, Type targetType) {
// note (issue #127): actually this should check for the presence of a matching constructor, with help of
// Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead we
// just check whether the target type is parameterized in a way that it implicitly should have a constructor
// which accepts the source type
TypeMirror sourceElementType = sourceType.getTypeParameters().isEmpty() ?
typeFactory.getType( Object.class ).getTypeMirror() :
sourceType.getTypeParameters().get( 0 ).getTypeMirror();
TypeMirror targetElementType = targetType.getTypeParameters().isEmpty() ?
typeFactory.getType( Object.class ).getTypeMirror() :
targetType.getTypeParameters().get( 0 ).getTypeMirror();
return typeUtils.isAssignable( sourceElementType, targetElementType );
}
/**
* Whether the given target type has a single-argument constructor which accepts the given source type.
*
* @param sourceType the source type
* @param targetType the target type
*
* @return {@code true} if the target type has a constructor accepting the given source type, {@code false}
* otherwise.
*/
private boolean mapTypeHasCompatibleConstructor(Type sourceType, Type targetType) {
// note (issue #127): actually this should check for the presence of a matching constructor, with help of
// Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead we
// just check whether the target type is parameterized in a way that it implicitly should have a constructor
// which accepts the source type
TypeMirror sourceKeyType = null;
TypeMirror targetKeyType = null;
TypeMirror sourceValueType = null;
TypeMirror targetValueType = null;
if ( sourceType.getTypeParameters().isEmpty() ) {
sourceKeyType = typeFactory.getType( Object.class ).getTypeMirror();
sourceValueType = typeFactory.getType( Object.class ).getTypeMirror();
}
else {
sourceKeyType = sourceType.getTypeParameters().get( 0 ).getTypeMirror();
sourceValueType = sourceType.getTypeParameters().get( 1 ).getTypeMirror();
}
if ( targetType.getTypeParameters().isEmpty() ) {
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 typeUtils.isAssignable( sourceKeyType, targetKeyType ) &&
typeUtils.isAssignable( sourceValueType, targetValueType );
}
}