#385 Introduction of BeanMapping annotation and target type selection, reorder of selectors

This commit is contained in:
sjaakd 2015-01-10 08:56:38 +01:00
parent 65c272286f
commit 4e771244ad
16 changed files with 535 additions and 17 deletions

View File

@ -0,0 +1,43 @@
/**
* Copyright 2012-2015 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Configures the mapping between two bean types
*
* @author Sjaak Derksen
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface BeanMapping {
/**
* Specifies the result type of the method to select in case ambiguous mappings / factory methods.
*
*
* @return the resultType to select
*/
Class resultType() default void.class;
}

View File

@ -42,9 +42,10 @@ public class MethodSelectors implements MethodSelector {
selectors = selectors =
Arrays.<MethodSelector>asList( Arrays.<MethodSelector>asList(
new TypeSelector( typeFactory ), new TypeSelector( typeFactory ),
new InheritanceSelector(), new QualifierSelector( typeUtils, elementUtils ),
new TargetTypeSelector( typeUtils, elementUtils ),
new XmlElementDeclSelector( typeUtils ), new XmlElementDeclSelector( typeUtils ),
new QualifierSelector( typeUtils, elementUtils ) new InheritanceSelector()
); );
} }

View File

@ -30,10 +30,12 @@ public class SelectionCriteria {
private final List<TypeMirror> qualifiers; private final List<TypeMirror> qualifiers;
private final String targetPropertyName; private final String targetPropertyName;
private final TypeMirror qualifyingResultType;
public SelectionCriteria(List<TypeMirror> qualifiers, String targetPropertyName) { public SelectionCriteria(List<TypeMirror> qualifiers, String targetPropertyName, TypeMirror qualifyingResultType) {
this.qualifiers = qualifiers; this.qualifiers = qualifiers;
this.targetPropertyName = targetPropertyName; this.targetPropertyName = targetPropertyName;
this.qualifyingResultType = qualifyingResultType;
} }
public List<TypeMirror> getQualifiers() { public List<TypeMirror> getQualifiers() {
@ -44,4 +46,7 @@ public class SelectionCriteria {
return targetPropertyName; return targetPropertyName;
} }
public TypeMirror getQualifyingResultType() {
return qualifyingResultType;
}
} }

View File

@ -0,0 +1,69 @@
/**
* Copyright 2012-2015 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.source.selector;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
/**
* This selector selects a best match based on the result type.
* <p>
* Suppose: Sedan -> Car -> Vehicle, MotorCycle -> Vehicle
* By means of this selector one can pinpoint the exact desired return type (Sedan, Car, MotorCycle, Vehicle)
*
* @author Sjaak Derksen
*/
public class TargetTypeSelector implements MethodSelector {
private final Types typeUtils;
public TargetTypeSelector( Types typeUtils, Elements elementUtils ) {
this.typeUtils = typeUtils;
}
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType();
if ( qualifyingTypeMirror != null ) {
List<T> candidatesWithQualifyingTargetType = new ArrayList<T>();
for ( T method : methods ) {
TypeMirror resultTypeMirror = method.getResultType().getTypeElement().asType();
if ( typeUtils.isSameType( qualifyingTypeMirror, resultTypeMirror ) ) {
candidatesWithQualifyingTargetType.add( method );
}
}
return candidatesWithQualifyingTargetType;
}
else {
return methods;
}
}
}

View File

@ -22,6 +22,7 @@ import javax.xml.bind.annotation.XmlElementDecl;
import net.java.dev.hickory.prism.GeneratePrism; import net.java.dev.hickory.prism.GeneratePrism;
import net.java.dev.hickory.prism.GeneratePrisms; import net.java.dev.hickory.prism.GeneratePrisms;
import org.mapstruct.BeanMapping;
import org.mapstruct.DecoratedWith; import org.mapstruct.DecoratedWith;
import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritConfiguration;
import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.InheritInverseConfiguration;
@ -46,6 +47,7 @@ import org.mapstruct.TargetType;
@GeneratePrism(value = Mapping.class, publicAccess = true), @GeneratePrism(value = Mapping.class, publicAccess = true),
@GeneratePrism(value = Mappings.class, publicAccess = true), @GeneratePrism(value = Mappings.class, publicAccess = true),
@GeneratePrism(value = IterableMapping.class, publicAccess = true), @GeneratePrism(value = IterableMapping.class, publicAccess = true),
@GeneratePrism(value = BeanMapping.class, publicAccess = true),
@GeneratePrism(value = MapMapping.class, publicAccess = true), @GeneratePrism(value = MapMapping.class, publicAccess = true),
@GeneratePrism(value = TargetType.class, publicAccess = true), @GeneratePrism(value = TargetType.class, publicAccess = true),
@GeneratePrism(value = MappingTarget.class, publicAccess = true), @GeneratePrism(value = MappingTarget.class, publicAccess = true),

View File

@ -47,6 +47,7 @@ import org.mapstruct.ap.model.source.builtin.BuiltInMappingMethods;
import org.mapstruct.ap.model.source.builtin.BuiltInMethod; import org.mapstruct.ap.model.source.builtin.BuiltInMethod;
import org.mapstruct.ap.model.source.selector.MethodSelectors; import org.mapstruct.ap.model.source.selector.MethodSelectors;
import org.mapstruct.ap.model.source.selector.SelectionCriteria; import org.mapstruct.ap.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.prism.BeanMappingPrism;
import org.mapstruct.ap.util.Strings; import org.mapstruct.ap.util.Strings;
/** /**
@ -124,14 +125,15 @@ public class MappingResolverImpl implements MappingResolver {
List<TypeMirror> qualifiers, List<TypeMirror> qualifiers,
String sourceReference) { String sourceReference) {
SelectionCriteria criteria = new SelectionCriteria(qualifiers, targetPropertyName, null );
ResolvingAttempt attempt = new ResolvingAttempt( ResolvingAttempt attempt = new ResolvingAttempt(
sourceModel, sourceModel,
mappingMethod, mappingMethod,
mappedElement, mappedElement,
targetPropertyName,
dateFormat, dateFormat,
qualifiers, sourceReference,
sourceReference criteria
); );
return attempt.getTargetAssignment( sourceType, targetType ); return attempt.getTargetAssignment( sourceType, targetType );
@ -145,14 +147,20 @@ public class MappingResolverImpl implements MappingResolver {
@Override @Override
public MethodReference getFactoryMethod( Method mappingMethod, Type targetType ) { public MethodReference getFactoryMethod( Method mappingMethod, Type targetType ) {
TypeMirror qualifyingResultTypeMirror = null;
BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( mappingMethod.getExecutable() );
if ( beanMappingPrism != null ) {
qualifyingResultTypeMirror = beanMappingPrism.resultType();
}
SelectionCriteria criteria = new SelectionCriteria(null, null, qualifyingResultTypeMirror );
ResolvingAttempt attempt = new ResolvingAttempt( ResolvingAttempt attempt = new ResolvingAttempt(
sourceModel, sourceModel,
mappingMethod, mappingMethod,
null, null,
null, null,
null, null,
null, criteria
null
); );
SourceMethod matchingSourceMethod = attempt.getBestMatch( sourceModel, null, targetType ); SourceMethod matchingSourceMethod = attempt.getBestMatch( sourceModel, null, targetType );
@ -169,9 +177,8 @@ public class MappingResolverImpl implements MappingResolver {
private final Method mappingMethod; private final Method mappingMethod;
private final String mappedElement; private final String mappedElement;
private final List<SourceMethod> methods; private final List<SourceMethod> methods;
private final String targetPropertyName;
private final String dateFormat; private final String dateFormat;
private final List<TypeMirror> qualifiers; private final SelectionCriteria selectionCriteria;
private final String sourceReference; private final String sourceReference;
// resolving via 2 steps creates the possibillity of wrong matches, first builtin method matches, // resolving via 2 steps creates the possibillity of wrong matches, first builtin method matches,
@ -182,18 +189,16 @@ public class MappingResolverImpl implements MappingResolver {
private ResolvingAttempt(List<SourceMethod> sourceModel, private ResolvingAttempt(List<SourceMethod> sourceModel,
Method mappingMethod, Method mappingMethod,
String mappedElement, String mappedElement,
String targetPropertyName,
String dateFormat, String dateFormat,
List<TypeMirror> qualifiers, String sourceReference,
String sourceReference) { SelectionCriteria criteria) {
this.mappingMethod = mappingMethod; this.mappingMethod = mappingMethod;
this.mappedElement = mappedElement; this.mappedElement = mappedElement;
this.methods = filterPossibleCandidateMethods( sourceModel ); this.methods = filterPossibleCandidateMethods( sourceModel );
this.targetPropertyName = targetPropertyName;
this.dateFormat = dateFormat; this.dateFormat = dateFormat;
this.qualifiers = qualifiers;
this.sourceReference = sourceReference; this.sourceReference = sourceReference;
this.virtualMethodCandidates = new HashSet<VirtualMappingMethod>(); this.virtualMethodCandidates = new HashSet<VirtualMappingMethod>();
this.selectionCriteria = criteria;
} }
private <T extends Method> List<T> filterPossibleCandidateMethods(List<T> candidateMethods) { private <T extends Method> List<T> filterPossibleCandidateMethods(List<T> candidateMethods) {
@ -440,13 +445,12 @@ public class MappingResolverImpl implements MappingResolver {
private <T extends Method> T getBestMatch(List<T> methods, Type sourceType, Type returnType) { private <T extends Method> T getBestMatch(List<T> methods, Type sourceType, Type returnType) {
SelectionCriteria criteria = new SelectionCriteria( qualifiers, targetPropertyName );
List<T> candidates = methodSelectors.getMatchingMethods( List<T> candidates = methodSelectors.getMatchingMethods(
mappingMethod, mappingMethod,
methods, methods,
sourceType, sourceType,
returnType, returnType,
criteria selectionCriteria
); );
// raise an error if more than one mapping method is suitable to map the given source type // raise an error if more than one mapping method is suitable to map the given source type

View File

@ -0,0 +1,31 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
/**
*
* @author Sjaak Derksen
*/
public class Apple extends Fruit {
public Apple(String type) {
super( type );
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
/**
*
* @author Sjaak Derksen
*/
public class AppleDto extends FruitDto {
public AppleDto(String type) {
super( type );
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
/**
*
* @author Sjaak Derksen
*/
public class Banana extends Fruit {
public Banana(String type) {
super( type );
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
/**
*
* @author Sjaak Derksen
*/
public class BananaDto extends FruitDto {
public BananaDto(String type) {
super( type );
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
/**
*
* @author Sjaak Derksen
*/
public class ConflictingFruitFactory {
public Apple createApple() {
return new Apple( "apple" );
}
public Banana createBanana() {
return new Banana( "banana" );
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
*
* @author Sjaak Derksen
*/
@Mapper(uses = ConflictingFruitFactory.class)
public interface ErroneousFruitMapper {
ErroneousFruitMapper INSTANCE = Mappers.getMapper( ErroneousFruitMapper.class );
@Mapping(target = "type", ignore = true)
Fruit map(FruitDto source);
}

View File

@ -0,0 +1,42 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
/**
*
* @author Sjaak Derksen
*/
public class Fruit {
private String type;
public Fruit(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
/**
*
* @author Sjaak Derksen
*/
public class FruitDto {
private String type;
public FruitDto(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@ -0,0 +1,74 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
import javax.tools.Diagnostic.Kind;
import static org.fest.assertions.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
/**
*
* @author Sjaak Derksen
*/
@IssueKey("385")
@WithClasses({
Fruit.class,
FruitDto.class,
Apple.class,
AppleDto.class
})
@RunWith(AnnotationProcessorTestRunner.class)
public class InheritanceSelectionTest {
@Test
@WithClasses( { ConflictingFruitFactory.class, ErroneousFruitMapper.class, Banana.class } )
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousFruitMapper.class,
kind = Kind.ERROR,
line = 36,
messageRegExp = "Ambiguous mapping methods found for factorizing .*Fruit: "
+ ".*Apple .*ConflictingFruitFactory\\.createApple\\(\\), "
+ ".*Banana .*ConflictingFruitFactory\\.createBanana\\(\\)\\.")
}
)
public void testForkedInheritanceHierarchyShouldResultInAmbigousMappingMethod() {
}
@Test
@WithClasses( { ConflictingFruitFactory.class, TargetTypeSelectingFruitMapper.class, Banana.class } )
public void testForkedInheritanceHierarchyButDefinedTargetType() {
FruitDto fruitDto = new FruitDto( null );
Fruit fruit = TargetTypeSelectingFruitMapper.INSTANCE.map( fruitDto );
assertThat( fruit ).isNotNull();
assertThat( fruit.getType() ).isEqualTo( "apple" );
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2012-2015 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.test.selection.inheritance;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
*
* @author Sjaak Derksen
*/
@Mapper(uses = ConflictingFruitFactory.class)
public interface TargetTypeSelectingFruitMapper {
TargetTypeSelectingFruitMapper INSTANCE = Mappers.getMapper( TargetTypeSelectingFruitMapper.class );
@BeanMapping(resultType = Apple.class)
@Mapping(target = "type", ignore = true)
Fruit map(FruitDto source);
}