diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java index ed46f294e..6420e11ae 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java @@ -35,10 +35,12 @@ import org.mapstruct.ap.model.PropertyMapping.JavaExpressionMappingBuilder; import org.mapstruct.ap.model.PropertyMapping.PropertyMappingBuilder; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.source.BeanMapping; import org.mapstruct.ap.model.source.Mapping; import org.mapstruct.ap.model.source.SourceMethod; import org.mapstruct.ap.model.source.SourceReference; import org.mapstruct.ap.option.ReportingPolicy; +import org.mapstruct.ap.prism.BeanMappingPrism; import org.mapstruct.ap.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.prism.NullValueMappingPrism; import org.mapstruct.ap.util.Executables; @@ -59,6 +61,7 @@ public class BeanMappingMethod extends MappingMethod { private final List constantMappings; private final MethodReference factoryMethod; private final boolean mapNullToDefault; + private final Type resultType; public static class Builder { @@ -106,7 +109,26 @@ public class BeanMappingMethod extends MappingMethod { MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ).isMapToDefault( prism ); MethodReference factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType() ); - return new BeanMappingMethod( method, propertyMappings, factoryMethod, mapNullToDefault ); + + // if there's no factory method, try the resultType in the @BeanMapping + Type resultType = null; + if ( factoryMethod == null ) { + BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); + BeanMapping beanMapping + = BeanMapping.fromPrism( beanMappingPrism, method.getExecutable(), ctx.getMessager() ); + if ( beanMapping != null && beanMapping.getResultType() != null ) { + resultType = ctx.getTypeFactory().getType( beanMapping.getResultType() ); + if ( !resultType.isAssignableTo( method.getResultType() ) ) { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( "%s not assignable to: %s.", resultType, method.getResultType() ), + method.getExecutable(), + beanMappingPrism.mirror ); + } + } + } + + return new BeanMappingMethod( method, propertyMappings, factoryMethod, mapNullToDefault, resultType ); } /** @@ -429,7 +451,8 @@ public class BeanMappingMethod extends MappingMethod { private BeanMappingMethod(SourceMethod method, List propertyMappings, MethodReference factoryMethod, - boolean mapNullToDefault) { + boolean mapNullToDefault, + Type resultType ) { super( method ); this.propertyMappings = propertyMappings; @@ -449,6 +472,7 @@ public class BeanMappingMethod extends MappingMethod { } this.factoryMethod = factoryMethod; this.mapNullToDefault = mapNullToDefault; + this.resultType = resultType; } public List getPropertyMappings() { @@ -467,6 +491,16 @@ public class BeanMappingMethod extends MappingMethod { return mapNullToDefault; } + @Override + public Type getResultType() { + if ( resultType == null ) { + return super.getResultType(); + } + else { + return resultType; + } + } + @Override public Set getImportTypes() { Set types = super.getImportTypes(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/Apple.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/Apple.java index 7fea06efb..fe273b5fe 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/Apple.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/Apple.java @@ -24,6 +24,11 @@ package org.mapstruct.ap.test.selection.resulttype; */ public class Apple extends Fruit { + + public Apple() { + super( "constructed-by-constructor" ); + } + public Apple(String type) { super( type ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousFruitMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousFruitMapper2.java new file mode 100644 index 000000000..2a113b53f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousFruitMapper2.java @@ -0,0 +1,39 @@ +/** + * 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.resulttype; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ErroneousFruitMapper2 { + + ErroneousFruitMapper2 INSTANCE = Mappers.getMapper( ErroneousFruitMapper2.class ); + + @BeanMapping(resultType = Banana.class) + @Mapping(target = "type", ignore = true) + Apple map(AppleDto source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java index 8d5d72339..374a76722 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java @@ -65,16 +65,42 @@ public class InheritanceSelectionTest { } @Test - @WithClasses( { ConflictingFruitFactory.class, TargetTypeSelectingFruitMapper.class, Banana.class } ) - public void testForkedInheritanceHierarchyButDefinedTargetType() { + @WithClasses( { ConflictingFruitFactory.class, ResultTypeSelectingFruitMapper.class, Banana.class } ) + public void testResultTypeBasedFactoryMethodSelection() { FruitDto fruitDto = new FruitDto( null ); - Fruit fruit = TargetTypeSelectingFruitMapper.INSTANCE.map( fruitDto ); + Fruit fruit = ResultTypeSelectingFruitMapper.INSTANCE.map( fruitDto ); assertThat( fruit ).isNotNull(); assertThat( fruit.getType() ).isEqualTo( "apple" ); } + @Test + @IssueKey("434") + @WithClasses( { ResultTypeConstructingFruitMapper.class } ) + public void testResultTypeBasedConstructionOfResult() { + + FruitDto fruitDto = new FruitDto( null ); + Fruit fruit = ResultTypeConstructingFruitMapper.INSTANCE.map( fruitDto ); + assertThat( fruit ).isNotNull(); + assertThat( fruit.getType() ).isEqualTo( "constructed-by-constructor" ); + } + + @Test + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousFruitMapper2.class, + kind = Kind.ERROR, + line = 35, + messageRegExp = ".*\\.Banana not assignable to: .*\\.Apple.") + } + ) + @IssueKey("434") + @WithClasses( { ErroneousFruitMapper2.class, Banana.class } ) + public void testResultTypeBasedConstructionOfResultNonAssignable() { + } + @Test @IssueKey("433") @WithClasses( { diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeConstructingFruitMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeConstructingFruitMapper.java new file mode 100644 index 000000000..aa0c6ac64 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeConstructingFruitMapper.java @@ -0,0 +1,39 @@ +/** + * 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.resulttype; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ResultTypeConstructingFruitMapper { + + ResultTypeConstructingFruitMapper INSTANCE = Mappers.getMapper( ResultTypeConstructingFruitMapper.class ); + + @BeanMapping(resultType = Apple.class) + @Mapping(target = "type", ignore = true) + Fruit map(FruitDto source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/TargetTypeSelectingFruitMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeSelectingFruitMapper.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/TargetTypeSelectingFruitMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeSelectingFruitMapper.java index ae00f6fa0..608809553 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/TargetTypeSelectingFruitMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ResultTypeSelectingFruitMapper.java @@ -28,10 +28,9 @@ import org.mapstruct.factory.Mappers; * @author Sjaak Derksen */ @Mapper(uses = ConflictingFruitFactory.class) +public interface ResultTypeSelectingFruitMapper { -public interface TargetTypeSelectingFruitMapper { - - TargetTypeSelectingFruitMapper INSTANCE = Mappers.getMapper( TargetTypeSelectingFruitMapper.class ); + ResultTypeSelectingFruitMapper INSTANCE = Mappers.getMapper( ResultTypeSelectingFruitMapper.class ); @BeanMapping(resultType = Apple.class) @Mapping(target = "type", ignore = true)