From 6ba04920efd1d4c686ed29df61976559d18fcf69 Mon Sep 17 00:00:00 2001 From: sjaakd Date: Sun, 25 Jan 2015 21:59:19 +0100 Subject: [PATCH] #432 Selection of factory method based on Qualifier --- .../main/java/org/mapstruct/BeanMapping.java | 12 +++ .../ap/model/source/BeanMapping.java | 76 +++++++++++++++++++ .../creation/MappingResolverImpl.java | 12 ++- .../ErroneousMovieFactoryMapper.java | 39 ++++++++++ .../qualifier/MovieFactoryMapper.java | 41 ++++++++++ .../selection/qualifier/QualifierTest.java | 55 ++++++++++++++ .../annotation/CreateGermanRelease.java | 35 +++++++++ .../qualifier/bean/ReleaseFactory.java | 37 +++++++++ 8 files changed, 303 insertions(+), 4 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/model/source/BeanMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/ErroneousMovieFactoryMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/MovieFactoryMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/annotation/CreateGermanRelease.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/bean/ReleaseFactory.java diff --git a/core-common/src/main/java/org/mapstruct/BeanMapping.java b/core-common/src/main/java/org/mapstruct/BeanMapping.java index ee1aadbcd..cf0a35388 100644 --- a/core-common/src/main/java/org/mapstruct/BeanMapping.java +++ b/core-common/src/main/java/org/mapstruct/BeanMapping.java @@ -18,6 +18,7 @@ */ package org.mapstruct; +import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -38,4 +39,15 @@ public @interface BeanMapping { * @return the resultType to select */ Class resultType() default void.class; + + /** + * A qualifier can be specified to aid the selection process of a suitable factory method. This is useful in + * case multiple factory method (hand written of internal) qualify and result in an 'Ambiguous factory methods' + * error. + * + * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. + * + * @return the qualifiers + */ + Class[] qualifiedBy() default { }; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/BeanMapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/BeanMapping.java new file mode 100644 index 000000000..d2a49fef4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/source/BeanMapping.java @@ -0,0 +1,76 @@ +/** + * 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; + +import java.util.List; +import javax.annotation.processing.Messager; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import org.mapstruct.ap.prism.BeanMappingPrism; + + +/** + * Represents an bean mapping as configured via {@code @BeanMapping}. + * + * @author Sjaak Derksen + */ +public class BeanMapping { + + private final List qualifiers; + private final TypeMirror resultType; + + public static BeanMapping fromPrism( BeanMappingPrism beanMapping, ExecutableElement method, Messager messager ) { + if ( beanMapping == null ) { + return null; + } + + boolean resultTypeIsDefined = !TypeKind.VOID.equals( beanMapping.resultType().getKind() ); + if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() ) { + messager.printMessage( + Diagnostic.Kind.ERROR, + "'resultType' and 'qualifiedBy' are are are undefined in @BeanMapping, " + + "define at least one of them.", + method + ); + } + + return new BeanMapping( + beanMapping.qualifiedBy(), + resultTypeIsDefined ? beanMapping.resultType() : null + ); + } + + private BeanMapping( + List qualifiers, + TypeMirror mirror) { + this.qualifiers = qualifiers; + this.resultType = mirror; + } + + public List getQualifiers() { + return qualifiers; + } + + public TypeMirror getResultType() { + return resultType; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolverImpl.java index ca08ba2fc..70ec8bc96 100755 --- a/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/creation/MappingResolverImpl.java @@ -41,6 +41,7 @@ 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.BeanMapping; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.SourceMethod; import org.mapstruct.ap.model.source.builtin.BuiltInMappingMethods; @@ -147,12 +148,15 @@ public class MappingResolverImpl implements MappingResolver { @Override public MethodReference getFactoryMethod( Method mappingMethod, Type targetType ) { - TypeMirror qualifyingResultTypeMirror = null; + TypeMirror resultType = null; + List qualifiers = null; BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( mappingMethod.getExecutable() ); - if ( beanMappingPrism != null ) { - qualifyingResultTypeMirror = beanMappingPrism.resultType(); + BeanMapping beanMapping = BeanMapping.fromPrism( beanMappingPrism, mappingMethod.getExecutable(), messager ); + if ( beanMapping != null ) { + resultType = beanMapping.getResultType(); + qualifiers = beanMapping.getQualifiers(); } - SelectionCriteria criteria = new SelectionCriteria(null, null, qualifyingResultTypeMirror ); + SelectionCriteria criteria = new SelectionCriteria(qualifiers, null, resultType ); ResolvingAttempt attempt = new ResolvingAttempt( sourceModel, diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/ErroneousMovieFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/ErroneousMovieFactoryMapper.java new file mode 100644 index 000000000..679db16aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/ErroneousMovieFactoryMapper.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.qualifier; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.selection.qualifier.bean.AbstractEntry; +import org.mapstruct.ap.test.selection.qualifier.bean.OriginalRelease; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ErroneousMovieFactoryMapper { + + ErroneousMovieFactoryMapper INSTANCE = Mappers.getMapper( ErroneousMovieFactoryMapper.class ); + + @BeanMapping + AbstractEntry toGerman( OriginalRelease movies ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/MovieFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/MovieFactoryMapper.java new file mode 100644 index 000000000..9527badff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/MovieFactoryMapper.java @@ -0,0 +1,41 @@ +/** + * 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.qualifier; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.selection.qualifier.annotation.CreateGermanRelease; +import org.mapstruct.ap.test.selection.qualifier.bean.AbstractEntry; +import org.mapstruct.ap.test.selection.qualifier.bean.OriginalRelease; +import org.mapstruct.ap.test.selection.qualifier.bean.ReleaseFactory; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( uses = ReleaseFactory.class ) +public interface MovieFactoryMapper { + + MovieFactoryMapper INSTANCE = Mappers.getMapper( MovieFactoryMapper.class ); + + @BeanMapping(qualifiedBy = CreateGermanRelease.class) + AbstractEntry toGerman( OriginalRelease movies ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java index bd37aa1a4..685bbf037 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java @@ -31,9 +31,11 @@ import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.MapAssert.entry; import org.junit.Test; import org.junit.runner.RunWith; +import org.mapstruct.ap.test.selection.qualifier.annotation.CreateGermanRelease; import org.mapstruct.ap.test.selection.qualifier.annotation.EnglishToGerman; import org.mapstruct.ap.test.selection.qualifier.annotation.NonQualifierAnnotated; import org.mapstruct.ap.test.selection.qualifier.annotation.TitleTranslator; +import org.mapstruct.ap.test.selection.qualifier.bean.ReleaseFactory; import org.mapstruct.ap.test.selection.qualifier.handwritten.Facts; import org.mapstruct.ap.test.selection.qualifier.handwritten.PlotWords; import org.mapstruct.ap.test.selection.qualifier.handwritten.Reverse; @@ -137,4 +139,57 @@ public class QualifierTest { assertThat( result.getTitle() ).isEqualTo( "ehT ,esneS htxiS"); } + + @Test + @WithClasses( { + MovieFactoryMapper.class, + ReleaseFactory.class, + CreateGermanRelease.class + }) + @IssueKey( "342") + public void testFactorySelectionWithQualifier() { + + OriginalRelease foreignMovies = new OriginalRelease(); + foreignMovies.setTitle( "Sixth Sense, The" ); + foreignMovies.setKeyWords( Arrays.asList( "evergreen", "magnificent" ) ); + Map> facts = new HashMap>(); + facts.put( "director", Arrays.asList( "M. Night Shyamalan" ) ); + facts.put( "cast", Arrays.asList( "Bruce Willis", "Haley Joel Osment", "Toni Collette" ) ); + facts.put( "plot keywords", Arrays.asList( "boy", "child psychologist", "I see dead people" ) ); + foreignMovies.setFacts( facts ); + + AbstractEntry abstractEntry = MovieFactoryMapper.INSTANCE.toGerman( foreignMovies ); + assertThat( abstractEntry ).isNotNull(); + assertThat( abstractEntry ).isInstanceOf( GermanRelease.class ); + assertThat( abstractEntry.getTitle() ).isEqualTo( "Sixth Sense, The" ); + assertThat( abstractEntry.getKeyWords() ).isNotNull(); + assertThat( abstractEntry.getKeyWords().size() ).isEqualTo( 2 ); + assertThat( abstractEntry.getKeyWords() ).containsSequence( "evergreen", "magnificent" ); + + assertThat( abstractEntry.getFacts() ).isNotNull(); + assertThat( abstractEntry.getFacts() ).hasSize( 3 ); + assertThat( abstractEntry.getFacts() ).includes( + entry( "director", Arrays.asList( "M. Night Shyamalan" ) ), + entry( "cast", Arrays.asList( "Bruce Willis", "Haley Joel Osment", "Toni Collette" ) ), + entry( "plot keywords", Arrays.asList( "boy", "child psychologist", "I see dead people" ) ) + ); + } + + @Test + @IssueKey( "342") + @WithClasses( { + ErroneousMovieFactoryMapper.class + } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( type = ErroneousMovieFactoryMapper.class, + kind = Kind.ERROR, + line = 37, + messageRegExp = "'resultType' and 'qualifiedBy' are are are undefined in @BeanMapping, " + + "define at least one of them." ) + } + ) + public void testEmptyBeanMapping() { + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/annotation/CreateGermanRelease.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/annotation/CreateGermanRelease.java new file mode 100644 index 000000000..b35df2d49 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/annotation/CreateGermanRelease.java @@ -0,0 +1,35 @@ +/** + * 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.qualifier.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.mapstruct.Qualifier; + +/** + * + * @author Sjaak Derksen + */ +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface CreateGermanRelease { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/bean/ReleaseFactory.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/bean/ReleaseFactory.java new file mode 100644 index 000000000..cc13a16cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/bean/ReleaseFactory.java @@ -0,0 +1,37 @@ +/** + * 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.qualifier.bean; + +import org.mapstruct.ap.test.selection.qualifier.annotation.CreateGermanRelease; + +/** + * + * @author Sjaak Derksen + */ +public class ReleaseFactory { + + public OriginalRelease createOriginalRelease() { + return new OriginalRelease(); + } + + @CreateGermanRelease + public GermanRelease createGermanRelease() { + return new GermanRelease(); + } +}