#432 Selection of factory method based on Qualifier

This commit is contained in:
sjaakd 2015-01-25 21:59:19 +01:00
parent bd89a054e8
commit 6ba04920ef
8 changed files with 303 additions and 4 deletions

View File

@ -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<? extends Annotation>[] qualifiedBy() default { };
}

View File

@ -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<TypeMirror> 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<TypeMirror> qualifiers,
TypeMirror mirror) {
this.qualifiers = qualifiers;
this.resultType = mirror;
}
public List<TypeMirror> getQualifiers() {
return qualifiers;
}
public TypeMirror getResultType() {
return resultType;
}
}

View File

@ -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<TypeMirror> 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,

View File

@ -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 );
}

View File

@ -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 );
}

View File

@ -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<String, List<String>> facts = new HashMap<String, List<String>>();
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() {
}
}

View File

@ -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 {
}

View File

@ -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();
}
}