From 6f19d5615500d66193f34244381db1e31b6856d9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 Oct 2018 15:22:25 +0100 Subject: [PATCH] #1566, #1253 Add support for initializing the AccessorNamingStrategy with Elements and Types and use Types for determining fluent setters * This allows using generic builders --- .../org/mapstruct/ap/MappingProcessor.java | 5 ++- .../util/AnnotationProcessorContext.java | 19 +++++++- .../ap/spi/AccessorNamingStrategy.java | 9 ++++ .../ap/spi/DefaultAccessorNamingStrategy.java | 14 +++++- .../spi/MapStructProcessingEnvironment.java | 37 +++++++++++++++ .../ap/test/bugs/_1566/AbstractBuilder.java | 19 ++++++++ .../ap/test/bugs/_1566/Issue1566Mapper.java | 20 +++++++++ .../ap/test/bugs/_1566/Issue1566Test.java | 41 +++++++++++++++++ .../mapstruct/ap/test/bugs/_1566/Source.java | 31 +++++++++++++ .../mapstruct/ap/test/bugs/_1566/Target.java | 45 +++++++++++++++++++ 10 files changed, 235 insertions(+), 5 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/AbstractBuilder.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index c277ba5c3..83841db52 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -114,7 +114,10 @@ public class MappingProcessor extends AbstractProcessor { super.init( processingEnv ); options = createOptions(); - annotationProcessorContext = new AnnotationProcessorContext( processingEnv.getElementUtils() ); + annotationProcessorContext = new AnnotationProcessorContext( + processingEnv.getElementUtils(), + processingEnv.getTypeUtils() + ); } private Options createOptions() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java index c38a1c11e..b6bcc85b4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java @@ -12,6 +12,7 @@ import java.util.ServiceLoader; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; @@ -20,13 +21,14 @@ import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; import org.mapstruct.ap.spi.DefaultBuilderProvider; import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; import org.mapstruct.ap.spi.ImmutablesBuilderProvider; +import org.mapstruct.ap.spi.MapStructProcessingEnvironment; /** * Keeps contextual data in the scope of the entire annotation processor ("application scope"). * * @author Gunnar Morling */ -public class AnnotationProcessorContext { +public class AnnotationProcessorContext implements MapStructProcessingEnvironment { private List astModifyingAnnotationProcessors; @@ -36,11 +38,13 @@ public class AnnotationProcessorContext { private AccessorNamingUtils accessorNaming; private Elements elementUtils; + private Types typeUtils; - public AnnotationProcessorContext(Elements elementUtils) { + public AnnotationProcessorContext(Elements elementUtils, Types typeUtils) { astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList( findAstModifyingAnnotationProcessors() ); this.elementUtils = elementUtils; + this.typeUtils = typeUtils; } /** @@ -67,6 +71,7 @@ public class AnnotationProcessorContext { defaultBuilderProvider = new ImmutablesBuilderProvider(); } this.accessorNamingStrategy = Services.get( AccessorNamingStrategy.class, defaultAccessorNamingStrategy ); + this.accessorNamingStrategy.init( this ); this.builderProvider = Services.get( BuilderProvider.class, defaultBuilderProvider ); this.accessorNaming = new AccessorNamingUtils( this.accessorNamingStrategy ); this.initialized = true; @@ -86,6 +91,16 @@ public class AnnotationProcessorContext { return processors; } + @Override + public Elements getElementUtils() { + return elementUtils; + } + + @Override + public Types getTypeUtils() { + return typeUtils; + } + public List getAstModifyingAnnotationProcessors() { return astModifyingAnnotationProcessors; } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java index 0afc22695..e4dd2cfe2 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java @@ -15,6 +15,15 @@ import javax.lang.model.element.ExecutableElement; */ public interface AccessorNamingStrategy { + /** + * Initializes the accessor naming strategy with the MapStruct processing environment. + * + * @param processingEnvironment environment for facilities + */ + default void init(MapStructProcessingEnvironment processingEnvironment) { + + } + /** * Returns the type of the given method. * diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index ab0d604a6..6a8c9baaf 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -12,8 +12,10 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleElementVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.Types; import org.mapstruct.ap.spi.util.IntrospectorUtils; @@ -26,6 +28,15 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + protected Elements elementUtils; + protected Types typeUtils; + + @Override + public void init(MapStructProcessingEnvironment processingEnvironment) { + this.elementUtils = processingEnvironment.getElementUtils(); + this.typeUtils = processingEnvironment.getTypeUtils(); + } + @Override public MethodType getMethodType(ExecutableElement method) { if ( isGetterMethod( method ) ) { @@ -90,8 +101,7 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { return method.getParameters().size() == 1 && !JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() && !isAdderWithUpperCase4thCharacter( method ) && - //TODO The Types need to be compared with Types#isSameType(TypeMirror, TypeMirror) - method.getReturnType().toString().equals( method.getEnclosingElement().asType().toString() ); + typeUtils.isAssignable( method.getReturnType(), method.getEnclosingElement().asType() ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java new file mode 100644 index 000000000..97fd42148 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.spi; + +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +/** + * MapStruct will provide the implementations of its SPIs with on object implementing this interface so they can use + * facilities provided by it. It is a subset of {@link javax.annotation.processing.ProcessingEnvironment + * ProcessingEnvironment}. + * + * @author Filip Hrisafov + * @see javax.annotation.processing.ProcessingEnvironment + */ +public interface MapStructProcessingEnvironment { + + /** + * Returns an implementation of some utility methods for + * operating on elements + * + * @return element utilities + */ + Elements getElementUtils(); + + /** + * Returns an implementation of some utility methods for + * operating on types. + * + * @return type utilities + */ + Types getTypeUtils(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/AbstractBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/AbstractBuilder.java new file mode 100644 index 000000000..d97fccf9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/AbstractBuilder.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1566; + +/** + * @author Filip Hrisafov + */ +public abstract class AbstractBuilder> { + String id; + + @SuppressWarnings("unchecked") + public T id(final String id) { + this.id = id; + return (T) this; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Mapper.java new file mode 100644 index 000000000..2ed6ed6d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Mapper.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1566; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1566Mapper { + + Issue1566Mapper INSTANCE = Mappers.getMapper( Issue1566Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Test.java new file mode 100644 index 000000000..667f90d1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Test.java @@ -0,0 +1,41 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1566; + +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.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + AbstractBuilder.class, + Issue1566Mapper.class, + Source.class, + Target.class +}) +@IssueKey("1566") +@RunWith(AnnotationProcessorTestRunner.class) +public class Issue1566Test { + + @Test + public void genericMapperIsCorrectlyUsed() { + Source source = new Source(); + source.setId( "id-123" ); + source.setName( "Source" ); + + Target target = Issue1566Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "id-123" ); + assertThat( target.getName() ).isEqualTo( "Source" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Source.java new file mode 100644 index 000000000..73f3820d0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Source.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1566; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String id; + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Target.java new file mode 100644 index 000000000..64a336af3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Target.java @@ -0,0 +1,45 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1566; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String id; + private final String name; + + private Target(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Target build() { + return new Target( id, name ); + } + } +}