diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index 63a2a7265..607d6f5a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -124,7 +124,7 @@ public class MethodMatcher { } // check result type - if ( !matchResultType( resultType, candidateMethod.getResultType(), genericTypesMap ) ) { + if ( !matchResultType( resultType, genericTypesMap ) ) { return false; } @@ -167,19 +167,29 @@ public class MethodMatcher { return true; } - private boolean matchResultType(Type resultType, - Type candidateResultType, - Map genericTypesMap) { + private boolean matchResultType(Type resultType, Map genericTypesMap) { + Type candidateResultType = candidateMethod.getResultType(); if ( !isJavaLangObject( candidateResultType.getTypeMirror() ) && !candidateResultType.isVoid() ) { - TypeMatcher returnTypeMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + final Assignability visitedAssignability; + if ( candidateMethod.getReturnType().isVoid() ) { + // for void-methods, the result-type of the candidate needs to be assignable from the given result type + visitedAssignability = Assignability.VISITED_ASSIGNABLE_FROM; + } + else { + // for non-void methods, the result-type of the candidate needs to be assignable to the given result + // type + visitedAssignability = Assignability.VISITED_ASSIGNABLE_TO; + } + + TypeMatcher returnTypeMatcher = new TypeMatcher( visitedAssignability, genericTypesMap ); if ( !returnTypeMatcher.visit( candidateResultType.getTypeMirror(), resultType.getTypeMirror() ) ) { if ( resultType.isPrimitive() ) { TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) resultType.getTypeMirror() ).asType(); TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + new TypeMatcher( visitedAssignability, genericTypesMap ); if ( !boxedReturnTypeMatcher.visit( candidateResultType.getTypeMirror(), boxedType ) ) { return false; @@ -189,7 +199,7 @@ public class MethodMatcher { TypeMirror boxedCandidateReturnType = typeUtils.boxedClass( (PrimitiveType) candidateResultType.getTypeMirror() ).asType(); TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + new TypeMatcher( visitedAssignability, genericTypesMap ); if ( !boxedReturnTypeMatcher.visit( boxedCandidateReturnType, resultType.getTypeMirror() ) ) { return false; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CallbackMethodTypeMatchingTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CallbackMethodTypeMatchingTest.java new file mode 100644 index 000000000..f05fb8138 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CallbackMethodTypeMatchingTest.java @@ -0,0 +1,46 @@ +/** + * 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.callbacks.typematching; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.callbacks.typematching.CarMapper.CarDto; +import org.mapstruct.ap.test.callbacks.typematching.CarMapper.CarEntity; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.fest.assertions.Assertions.assertThat; + +/** + * @author Andreas Gudian + * + */ +@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses({ + CarMapper.class +}) +public class CallbackMethodTypeMatchingTest { + @Test + public void callbackMethodAreCalled() { + CarEntity carEntity = CarMapper.INSTANCE.toCarEntity( new CarDto() ); + + assertThat( carEntity.getId() ).isEqualTo( 2 ); + assertThat( carEntity.getSeatCount() ).isEqualTo( 5 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CarMapper.java new file mode 100644 index 000000000..7c61b428e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CarMapper.java @@ -0,0 +1,127 @@ +/** + * 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.callbacks.typematching; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + * + */ +@Mapper +public abstract class CarMapper { + + public static final CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + public abstract CarEntity toCarEntity(CarDto carDto); + + @AfterMapping + protected void neverMatched(ElectricCarDto electricDto) { + throw new RuntimeException( "must not be called" ); + } + + @AfterMapping + protected void neverMatched(@MappingTarget ElectricCarEntity electricEntity) { + throw new RuntimeException( "must not be called" ); + } + + @AfterMapping + protected void isCalled(@MappingTarget Object any) { + if ( any instanceof CarEntity ) { + CarEntity car = (CarEntity) any; + if ( car.getSeatCount() == 0 ) { + car.setSeatCount( 5 ); + } + } + } + + @AfterMapping + protected void incrementsTargetId(@MappingTarget Identifiable identifiable) { + identifiable.setId( identifiable.getId() + 1 ); + } + + @BeforeMapping + protected void incrementsSourceId(Identifiable identifiable) { + identifiable.setId( identifiable.getId() + 1 ); + } + + public abstract static class Identifiable { + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + } + + public static class CarDto extends Identifiable { + private int seatCount; + + public int getSeatCount() { + return seatCount; + } + + public void setSeatCount(int seatCount) { + this.seatCount = seatCount; + } + } + + public static class ElectricCarDto extends CarDto { + private long batteryCapacity; + + public long getBatteryCapacity() { + return batteryCapacity; + } + + public void setBatteryCapacity(long batteryCapacity) { + this.batteryCapacity = batteryCapacity; + } + } + + public static class CarEntity extends Identifiable { + private int seatCount; + + public int getSeatCount() { + return seatCount; + } + + public void setSeatCount(int seatCount) { + this.seatCount = seatCount; + } + } + + public static class ElectricCarEntity extends Identifiable { + private long batteryCapacity; + + public long getBatteryCapacity() { + return batteryCapacity; + } + + public void setBatteryCapacity(long batteryCapacity) { + this.batteryCapacity = batteryCapacity; + } + } +}