From b53741d9608c1a3faccc46c8ee32b54cfdc5d724 Mon Sep 17 00:00:00 2001 From: Sjaak Derksen Date: Sun, 17 Mar 2019 16:45:22 +0100 Subject: [PATCH] #37 Rudimentary logging in mapstruct (#1741) * #37 Rudimentary logging in mapstruct * #37 Rudimentary logging in mapstruct changed order * #37 rework * #37 documentation * #37 comments * #37 docmentation revisited * #37 review comments * #37 unit test * #37 unit test fixing empty mapper * #37 rework comments christian * #37 adding deferred mapper logging * #37 adding unit test for deferred mapper logging * #37 processing comments Filip --- .../mapstruct-reference-guide.asciidoc | 12 +- .../org/mapstruct/ap/MappingProcessor.java | 18 ++- .../model/ContainerMappingMethodBuilder.java | 7 + .../ap/internal/model/MapMappingMethod.java | 14 +- .../ap/internal/model/MethodReference.java | 13 ++ .../ap/internal/model/PropertyMapping.java | 9 ++ .../ap/internal/model/TypeConversion.java | 6 + .../mapstruct/ap/internal/option/Options.java | 8 +- .../DefaultModelElementProcessorContext.java | 16 ++- .../processor/MapperCreationProcessor.java | 9 +- .../processor/MethodRetrievalProcessor.java | 6 + .../util/AnnotationProcessorContext.java | 27 +++- .../ap/internal/util/FormattingMessager.java | 8 ++ .../mapstruct/ap/internal/util/Message.java | 17 +++ .../accessor/ExecutableElementAccessor.java | 5 + .../accessor/VariableElementAccessor.java | 5 + .../writer/IndentationCorrectingWriter.java | 2 +- .../ap/spi/DefaultBuilderProvider.java | 1 + .../FreeBuilderAccessorNamingStrategy.java | 1 + .../spi/ImmutablesAccessorNamingStrategy.java | 1 + .../ap/spi/ImmutablesBuilderProvider.java | 1 + .../mapstruct/ap/spi/NoOpBuilderProvider.java | 1 + .../common/DefaultConversionContextTest.java | 5 + ...AstModifyingAnnotationProcessorSaysNo.java | 18 +++ .../ap/test/verbose/CreateBeanMapping.java | 67 +++++++++ .../test/verbose/CreateIterableMapping.java | 45 ++++++ .../ap/test/verbose/CreateMapMapping.java | 69 +++++++++ .../ap/test/verbose/SelectBeanMapping.java | 69 +++++++++ .../test/verbose/SelectIterableMapping.java | 31 ++++ .../ap/test/verbose/SelectMapMapping.java | 40 ++++++ .../ap/test/verbose/SelectStreamMapping.java | 31 ++++ .../ap/test/verbose/ValueMapping.java | 22 +++ .../ap/test/verbose/VerboseTest.java | 134 ++++++++++++++++++ .../testutil/WithServiceImplementation.java | 2 + .../compilation/annotation/ExpectedNote.java | 41 ++++++ .../model/CompilationOutcomeDescriptor.java | 43 ++++-- .../testutil/runner/CompilingStatement.java | 33 ++++- 37 files changed, 812 insertions(+), 25 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/AstModifyingAnnotationProcessorSaysNo.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/CreateBeanMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/CreateIterableMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/CreateMapMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/SelectBeanMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/SelectIterableMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/SelectMapMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/SelectStreamMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/ValueMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 79ce0dc09..c985a90ec 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -223,6 +223,8 @@ When invoking javac directly, these options are passed to the compiler in the fo ${org.mapstruct.version} + + true -Amapstruct.suppressGeneratorTimestamp=true @@ -230,6 +232,9 @@ When invoking javac directly, these options are passed to the compiler in the fo -Amapstruct.suppressGeneratorVersionInfoComment=true + + -Amapstruct.verbose=true + @@ -246,7 +251,8 @@ When invoking javac directly, these options are passed to the compiler in the fo compileJava { options.compilerArgs = [ '-Amapstruct.suppressGeneratorTimestamp=true', - '-Amapstruct.suppressGeneratorVersionInfoComment=true' + '-Amapstruct.suppressGeneratorVersionInfoComment=true', + '-Amapstruct.verbose=true' ] } ... @@ -265,6 +271,10 @@ suppressGeneratorTimestamp` |If set to `true`, the creation of a time stamp in the `@Generated` annotation in the generated mapper classes is suppressed. |`false` +|`mapstruct.verbose` +|If set to `true`, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also `showWarnings` needs to be added due to a problem in the maven-compiler-plugin configuration. +|`false` + |`mapstruct. suppressGeneratorVersionInfoComment` |If set to `true`, the creation of the `comment` attribute in the `@Generated` annotation in the generated mapper classes is suppressed. The comment contains information about the version of MapStruct and about the compiler used for the annotation processing. diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index dc565e13b..4878f3f98 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -82,7 +82,8 @@ import static javax.lang.model.element.ElementKind.CLASS; MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, MappingProcessor.UNMAPPED_TARGET_POLICY, - MappingProcessor.DEFAULT_COMPONENT_MODEL + MappingProcessor.DEFAULT_COMPONENT_MODEL, + MappingProcessor.VERBOSE }) public class MappingProcessor extends AbstractProcessor { @@ -97,6 +98,7 @@ public class MappingProcessor extends AbstractProcessor { protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy"; protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; + protected static final String VERBOSE = "mapstruct.verbose"; private Options options; @@ -120,7 +122,9 @@ public class MappingProcessor extends AbstractProcessor { options = createOptions(); annotationProcessorContext = new AnnotationProcessorContext( processingEnv.getElementUtils(), - processingEnv.getTypeUtils() + processingEnv.getTypeUtils(), + processingEnv.getMessager(), + options.isVerbose() ); } @@ -132,7 +136,8 @@ public class MappingProcessor extends AbstractProcessor { Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), unmappedTargetPolicy != null ? ReportingPolicyPrism.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), - Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ) + Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), + Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) ) ); } @@ -221,6 +226,11 @@ public class MappingProcessor extends AbstractProcessor { processMapperTypeElement( context, mapperElement ); } catch ( TypeHierarchyErroneousException thie ) { + if ( options.isVerbose() ) { + processingEnv.getMessager().printMessage( + Kind.NOTE, "MapStruct: referred types not available (yet), deferring mapper: " + + mapperElement ); + } deferredMappers.add( mapperElement ); } catch ( Throwable t ) { @@ -242,7 +252,7 @@ public class MappingProcessor extends AbstractProcessor { StringWriter sw = new StringWriter(); thrown.printStackTrace( new PrintWriter( sw ) ); - String reportableStacktrace = sw.toString().replace( System.getProperty( "line.separator" ), " " ); + String reportableStacktrace = sw.toString().replace( System.lineSeparator( ), " " ); processingEnv.getMessager().printMessage( Kind.ERROR, "Internal error in the mapping processor: " + reportableStacktrace, element ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java index c265d9daa..72a1eb29f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java @@ -20,6 +20,7 @@ import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; +import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; /** @@ -94,6 +95,12 @@ public abstract class ContainerMappingMethodBuilder arguments = sourceParameters.stream() + .map( p -> p.isMappingContext() || p.isMappingTarget() || p.isTargetType() ? p.getName() : argument ) + .collect( Collectors.toList() ); + + return returnTypeAsString + " " + mapper + "#" + name + "(" + Strings.join( arguments, "," ) + ")"; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 229150403..423b5f4b4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -295,6 +295,9 @@ public class PropertyMapping extends ModelElement { // handle source this.rightHandSide = getSourceRHS( sourceReference ); + + ctx.getMessager().note( 2, Message.PROPERTYMAPPING_MAPPING_NOTE, rightHandSide, targetWriteAccessor ); + rightHandSide.setUseElementAsSourceTypeForMatching( targetWriteAccessorType == TargetWriteAccessorType.ADDER ); @@ -339,6 +342,12 @@ public class PropertyMapping extends ModelElement { else { assignment = forgeMapping( rightHandSide ); } + if ( assignment != null ) { + ctx.getMessager().note( 2, Message.PROPERTYMAPPING_CREATE_NOTE, assignment ); + } + } + else { + ctx.getMessager().note( 2, Message.PROPERTYMAPPING_SELECT_NOTE, assignment ); } if ( assignment != null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java index d0481cdc2..3f06ce7e8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java @@ -140,4 +140,10 @@ public class TypeConversion extends ModelElement implements Assignment { public boolean isCallingUpdateMethod() { return false; } + + @Override + public String toString() { + String argument = getAssignment() != null ? getAssignment().toString() : getSourceReference(); + return openExpression + argument + closeExpression; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 77001c481..1e555e8d3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -19,15 +19,17 @@ public class Options { private final ReportingPolicyPrism unmappedTargetPolicy; private final boolean alwaysGenerateSpi; private final String defaultComponentModel; + private final boolean verbose; public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, ReportingPolicyPrism unmappedTargetPolicy, - String defaultComponentModel, boolean alwaysGenerateSpi) { + String defaultComponentModel, boolean alwaysGenerateSpi, boolean verbose) { this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; this.unmappedTargetPolicy = unmappedTargetPolicy; this.defaultComponentModel = defaultComponentModel; this.alwaysGenerateSpi = alwaysGenerateSpi; + this.verbose = verbose; } public boolean isSuppressGeneratorTimestamp() { @@ -49,4 +51,8 @@ public class Options { public boolean isAlwaysGenerateSpi() { return alwaysGenerateSpi; } + + public boolean isVerbose() { + return verbose; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java index 5168dccc1..bc7374d39 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.processor; import java.util.Map; +import java.util.stream.IntStream; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; @@ -45,7 +46,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { RoundContext roundContext, Map notToBeImported) { this.processingEnvironment = processingEnvironment; - this.messager = new DelegatingMessager( processingEnvironment.getMessager() ); + this.messager = new DelegatingMessager( processingEnvironment.getMessager(), options.isVerbose() ); this.accessorNaming = roundContext.getAnnotationProcessorContext().getAccessorNaming(); this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); this.delegatingTypes = new TypesDecorator( processingEnvironment, versionInformation ); @@ -108,9 +109,11 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { private final Messager delegate; private boolean isErroneous = false; + private final boolean verbose; - DelegatingMessager(Messager delegate) { + DelegatingMessager(Messager delegate, boolean verbose) { this.delegate = delegate; + this.verbose = verbose; } @Override @@ -155,6 +158,15 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { } } + public void note( int level, Message msg, Object... args ) { + if ( verbose ) { + StringBuilder builder = new StringBuilder(); + IntStream.range( 0, level ).mapToObj( i -> "-" ).forEach( builder::append ); + builder.append( " MapStruct: " ).append( String.format( msg.getDescription(), args ) ); + delegate.printMessage( Kind.NOTE, builder.toString() ); + } + } + public boolean isErroneous() { return isErroneous; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 974822216..1b31bcf4c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -38,9 +38,9 @@ import org.mapstruct.ap.internal.model.MappingMethod; import org.mapstruct.ap.internal.model.StreamMappingMethod; import org.mapstruct.ap.internal.model.SupportingConstructorFragment; import org.mapstruct.ap.internal.model.ValueMappingMethod; +import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -286,6 +286,7 @@ public class MapperCreationProcessor implements ModelElementProcessor\" or \"\" can only be used once." ), VALUE_MAPPING_UNMAPPED_SOURCES( "The following constants from the %s enum have no corresponding constant in the %s enum and must be be mapped via adding additional mappings: %s." ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java index 356313f52..effd137b0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java @@ -28,4 +28,9 @@ public class ExecutableElementAccessor extends AbstractAccessor { public ExecutableElement getExecutable() { return null; } + + @Override + public String toString() { + return element.toString(); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java index 79d164ddb..25cea82c8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java @@ -37,7 +37,7 @@ class IndentationCorrectingWriter extends Writer { * Set to true to enable output of written characters on the console. */ private static final boolean DEBUG = false; - private static final String LINE_SEPARATOR = System.getProperty( "line.separator" ); + private static final String LINE_SEPARATOR = System.lineSeparator( ); private static final boolean IS_WINDOWS = System.getProperty( "os.name" ).startsWith( "Windows" ); private State currentState = State.START_OF_LINE; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 9921ad2f0..f5656e2e4 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -280,4 +280,5 @@ public class DefaultBuilderProvider implements BuilderProvider { protected boolean shouldIgnore(TypeElement typeElement) { return typeElement == null || JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName() ).matches(); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java index 5b269b4b0..92fef3a1b 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java @@ -35,4 +35,5 @@ public class FreeBuilderAccessorNamingStrategy extends DefaultAccessorNamingStra // with set return false; } + } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java index abe777a1c..69c66cdd2 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java @@ -23,4 +23,5 @@ public class ImmutablesAccessorNamingStrategy extends DefaultAccessorNamingStrat protected boolean isFluentSetter(ExecutableElement method) { return super.isFluentSetter( method ) && !method.getSimpleName().toString().equals( "from" ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java index 16ad797a9..d4ccd029e 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java @@ -83,4 +83,5 @@ public class ImmutablesBuilderProvider extends DefaultBuilderProvider { builderQualifiedName.append( "Immutable" ).append( typeElement.getSimpleName() ); return elementUtils.getTypeElement( builderQualifiedName ); } + } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/NoOpBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/NoOpBuilderProvider.java index fae086ab1..a30346c4b 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/NoOpBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/NoOpBuilderProvider.java @@ -23,5 +23,6 @@ public class NoOpBuilderProvider implements BuilderProvider { public BuilderInfo findBuilderInfo(TypeMirror type) { return null; } + } // end::documentation[] diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java index 6b40de313..76ceac40d 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java @@ -153,6 +153,11 @@ public class DefaultConversionContextTest { lastKindPrinted = msg.getDiagnosticKind(); } + @Override + public void note(int level, Message msg, Object... args) { + throw new UnsupportedOperationException( "Should not be called" ); + } + public Diagnostic.Kind getLastKindPrinted() { return lastKindPrinted; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/AstModifyingAnnotationProcessorSaysNo.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/AstModifyingAnnotationProcessorSaysNo.java new file mode 100644 index 000000000..898721b9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/AstModifyingAnnotationProcessorSaysNo.java @@ -0,0 +1,18 @@ +/* + * 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.verbose; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +public class AstModifyingAnnotationProcessorSaysNo implements AstModifyingAnnotationProcessor { + + @Override + public boolean isTypeComplete(TypeMirror type) { + return false; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateBeanMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateBeanMapping.java new file mode 100644 index 000000000..594318d9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateBeanMapping.java @@ -0,0 +1,67 @@ +/* + * 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.verbose; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CreateBeanMapping { + + CreateBeanMapping INSTANCE = Mappers.getMapper( CreateBeanMapping.class ); + + Target map(Source source); + + class Source { + private NestedSource nested; + + public NestedSource getNested() { + return nested; + } + + public void setNested(NestedSource nested) { + this.nested = nested; + } + } + + class Target { + private NestedTarget nested; + + public NestedTarget getNested() { + return nested; + } + + public void setNested(NestedTarget nested) { + this.nested = nested; + } + } + + class NestedSource { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class NestedTarget { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateIterableMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateIterableMapping.java new file mode 100644 index 000000000..cf06fede0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateIterableMapping.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.verbose; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CreateIterableMapping { + + CreateIterableMapping INSTANCE = Mappers.getMapper( CreateIterableMapping.class ); + + List map(List source); + + class SourceElement { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class TargetElement { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateMapMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateMapMapping.java new file mode 100644 index 000000000..815625689 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/CreateMapMapping.java @@ -0,0 +1,69 @@ +/* + * 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.verbose; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CreateMapMapping { + + CreateMapMapping INSTANCE = Mappers.getMapper( CreateMapMapping.class ); + + Map map(Map source); + + // empty beans fail.. TODO check + class SourceKey { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class SourceValue { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class TargetKey { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class TargetValue { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectBeanMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectBeanMapping.java new file mode 100644 index 000000000..4b6d73d30 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectBeanMapping.java @@ -0,0 +1,69 @@ +/* + * 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.verbose; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SelectBeanMapping { + + SelectBeanMapping INSTANCE = Mappers.getMapper( SelectBeanMapping.class ); + + Target map(Source source); + + NestedTarget map(NestedSource source); + + class Source { + private NestedSource nested; + + public NestedSource getNested() { + return nested; + } + + public void setNested(NestedSource nested) { + this.nested = nested; + } + } + + class Target { + private NestedTarget nested; + + public NestedTarget getNested() { + return nested; + } + + public void setNested(NestedTarget nested) { + this.nested = nested; + } + } + + class NestedSource { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class NestedTarget { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectIterableMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectIterableMapping.java new file mode 100644 index 000000000..fa492cc14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectIterableMapping.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.verbose; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SelectIterableMapping { + + SelectIterableMapping INSTANCE = Mappers.getMapper( SelectIterableMapping.class ); + + List map(List source); + + default TargetElement map(SourceElement sourceKey) { + return null; + } + + class SourceElement { + } + + class TargetElement { + } + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectMapMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectMapMapping.java new file mode 100644 index 000000000..7018642ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectMapMapping.java @@ -0,0 +1,40 @@ +/* + * 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.verbose; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SelectMapMapping { + + SelectMapMapping INSTANCE = Mappers.getMapper( SelectMapMapping.class ); + + Map map(Map source); + + default TargetKey map(SourceKey sourceKey) { + return null; + } + + default TargetValue map(SourceValue sourceValue) { + return null; + } + + class SourceKey { + } + + class SourceValue { + } + + class TargetKey { + } + + class TargetValue { + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectStreamMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectStreamMapping.java new file mode 100644 index 000000000..7ee44d8b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/SelectStreamMapping.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.verbose; + +import java.util.stream.Stream; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SelectStreamMapping { + + SelectStreamMapping INSTANCE = Mappers.getMapper( SelectStreamMapping.class ); + + Stream map(Stream source); + + default TargetElement map(SourceElement sourceKey) { + return null; + } + + class SourceElement { + } + + class TargetElement { + } + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/ValueMapping.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/ValueMapping.java new file mode 100644 index 000000000..f3e07fca8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/ValueMapping.java @@ -0,0 +1,22 @@ +/* + * 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.verbose; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ValueMapping { + + ValueMapping INSTANCE = Mappers.getMapper( ValueMapping.class ); + + TargetEnum map(SourceEnum source); + + enum TargetEnum { VALUE } + + enum SourceEnum { VALUE } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java b/processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java new file mode 100644 index 000000000..99b77b122 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/verbose/VerboseTest.java @@ -0,0 +1,134 @@ +/* + * 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.verbose; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.Compiler; +import org.mapstruct.ap.testutil.runner.DisabledOnCompiler; + +@IssueKey("37") +@RunWith(AnnotationProcessorTestRunner.class) +public class VerboseTest { + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(CreateBeanMapping.class) + @ExpectedNote("^MapStruct: Using accessor naming strategy:.*DefaultAccessorNamingStrategy.*$") + @ExpectedNote("^MapStruct: Using builder provider:.*DefaultBuilderProvider.*$") + @ExpectedNote("^ MapStruct: processing:.*.CreateBeanMapping.*$") + @ExpectedNote("^ MapStruct: applying mapper configuration:.*MapperConfiguration.*$") + public void testGeneralMessages() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @WithServiceImplementation(provides = BuilderProvider.class, value = ImmutablesBuilderProvider.class) + @WithServiceImplementation(provides = AccessorNamingStrategy.class, value = ImmutablesAccessorNamingStrategy.class) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(CreateBeanMapping.class) + @ExpectedNote("^MapStruct: Using accessor naming strategy:.*ImmutablesAccessorNamingStrategy.*$") + @ExpectedNote("^MapStruct: Using builder provider:.*ImmutablesBuilderProvider.*$") + @ExpectedNote("^ MapStruct: processing:.*.CreateBeanMapping.*$") + @ExpectedNote("^ MapStruct: applying mapper configuration:.*MapperConfiguration.*$") + public void testGeneralWithOtherSPI() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @WithServiceImplementation(provides = AstModifyingAnnotationProcessor.class, + value = AstModifyingAnnotationProcessorSaysNo.class) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(CreateBeanMapping.class) + @ExpectedNote("^MapStruct: referred types not available \\(yet\\), deferring mapper:.*CreateBeanMapping.*$") + public void testDeferred() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(CreateBeanMapping.class) + @ExpectedNote("^- MapStruct: creating bean mapping method implementation for.*$") + @ExpectedNote("^-- MapStruct: creating property mapping.*$") + public void testCreateBeanMapping() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(SelectBeanMapping.class) + @ExpectedNote("^- MapStruct: creating bean mapping method implementation for.*$") + @ExpectedNote("^-- MapStruct: selecting property mapping.*$") + public void testSelectBeanMapping() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(ValueMapping.class) + @ExpectedNote("^- MapStruct: creating value mapping method implementation for.*$") + public void testValueMapping() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(CreateIterableMapping.class) + @ExpectedNote("^- MapStruct: creating iterable mapping method implementation for.*$") + @ExpectedNote("^-- MapStruct: creating element mapping.*$") + public void testVerboseCreateIterableMapping() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(SelectIterableMapping.class) + @ExpectedNote("^- MapStruct: creating iterable mapping method implementation for.*$") + @ExpectedNote("^-- MapStruct: selecting element mapping.*$") + public void testVerboseSelectingIterableMapping() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(SelectStreamMapping.class) + @ExpectedNote("^- MapStruct: creating stream mapping method implementation for.*$") + public void testVerboseSelectingStreamMapping() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(CreateMapMapping.class) + @ExpectedNote("^- MapStruct: creating map mapping method implementation for.*$") + @ExpectedNote("^-- MapStruct: creating key mapping.*$") + @ExpectedNote("^-- MapStruct: creating value mapping.*$") + public void testVerboseCreateMapMapping() { + } + + @Test + @DisabledOnCompiler( Compiler.ECLIPSE ) + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses(SelectMapMapping.class) + @ExpectedNote("^- MapStruct: creating map mapping method implementation for.*$") + @ExpectedNote("^-- MapStruct: selecting key mapping.*$") + @ExpectedNote("^-- MapStruct: selecting value mapping.*$") + public void testVerboseSelectingMapMapping() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithServiceImplementation.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithServiceImplementation.java index 2397667eb..16d8ad04f 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/WithServiceImplementation.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithServiceImplementation.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.testutil; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -17,6 +18,7 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) +@Repeatable( WithServiceImplementations.class ) public @interface WithServiceImplementation { /** * @return The service implementation class that is to be made available during the annotation processing. diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java new file mode 100644 index 000000000..1cb95f52c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.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.testutil.compilation.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An expected {@link javax.tools.Diagnostic.Kind#NOTE}. + * + * @author Sjaak Derksen + */ + +@Repeatable(ExpectedNote.ExpectedNotes.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ExpectedNote { + + String value(); + + /** + * The notes in the order they are expected + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface ExpectedNotes { + + /** + * Regexp for the note to match. + * + * @return + */ + ExpectedNote[] value(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java index cfe201f5d..22d9bcd68 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java @@ -8,6 +8,8 @@ package org.mapstruct.ap.testutil.compilation.model; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; @@ -17,6 +19,7 @@ import org.codehaus.plexus.compiler.CompilerMessage; import org.codehaus.plexus.compiler.CompilerResult; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; /** * Represents the outcome of a compilation. @@ -25,21 +28,37 @@ import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutco */ public class CompilationOutcomeDescriptor { + private static final String LINE_SEPARATOR = System.lineSeparator( ); + private CompilationResult compilationResult; private List diagnostics; + private List notes; private CompilationOutcomeDescriptor(CompilationResult compilationResult, - List diagnostics) { + List diagnostics, + List notes) { this.compilationResult = compilationResult; this.diagnostics = diagnostics; + this.notes = notes; } public static CompilationOutcomeDescriptor forExpectedCompilationResult( - ExpectedCompilationOutcome expectedCompilationResult) { + ExpectedCompilationOutcome expectedCompilationResult, ExpectedNote.ExpectedNotes expectedNotes, + ExpectedNote expectedNote) { + List notes = new ArrayList<>(); + if ( expectedNotes != null ) { + notes.addAll( Stream.of( expectedNotes.value() ) + .map( ExpectedNote::value ) + .collect( Collectors.toList() ) ); + } + if ( expectedNote != null ) { + notes.add( expectedNote.value() ); + } if ( expectedCompilationResult == null ) { return new CompilationOutcomeDescriptor( CompilationResult.SUCCEEDED, - Collections.emptyList() + Collections.emptyList(), + notes ); } else { @@ -48,8 +67,7 @@ public class CompilationOutcomeDescriptor { expectedCompilationResult.diagnostics() ) { diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) ); } - - return new CompilationOutcomeDescriptor( expectedCompilationResult.value(), diagnosticDescriptors ); + return new CompilationOutcomeDescriptor( expectedCompilationResult.value(), diagnosticDescriptors, notes ); } } @@ -57,31 +75,34 @@ public class CompilationOutcomeDescriptor { List> diagnostics) { CompilationResult compilationResult = compilationSuccessful ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; - + List notes = new ArrayList<>(); List diagnosticDescriptors = new ArrayList(); for ( Diagnostic diagnostic : diagnostics ) { //ignore notes created by the compiler if ( diagnostic.getKind() != Kind.NOTE ) { diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( sourceDir, diagnostic ) ); } + else { + notes.add( diagnostic.getMessage( null ) ); + } } - return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors ); + return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, notes ); } public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerResult compilerResult) { CompilationResult compilationResult = compilerResult.isSuccess() ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; - List diagnosticDescriptors = new ArrayList(); for ( CompilerMessage message : compilerResult.getCompilerMessages() ) { if ( message.getKind() != CompilerMessage.Kind.NOTE ) { diagnosticDescriptors.add( DiagnosticDescriptor.forCompilerMessage( sourceDir, message ) ); } + // the eclipse compiler does not support NOTE (it is never actually set). } - return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors ); + return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, Collections.emptyList() ); } public CompilationResult getCompilationResult() { @@ -92,6 +113,10 @@ public class CompilationOutcomeDescriptor { return diagnostics; } + public List getNotes() { + return notes; + } + @Override public int hashCode() { final int prime = 31; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java index f6ddcbd2b..a292ef6a2 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; @@ -32,6 +33,7 @@ import org.mapstruct.ap.testutil.WithServiceImplementations; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.DisableCheckstyle; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOptions; import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; @@ -54,7 +56,7 @@ abstract class CompilingStatement extends Statement { private static final String TARGET_COMPILATION_TESTS = "/target/compilation-tests/"; - private static final String LINE_SEPARATOR = System.getProperty( "line.separator" ); + private static final String LINE_SEPARATOR = System.lineSeparator( ); private static final DiagnosticDescriptorComparator COMPARATOR = new DiagnosticDescriptorComparator(); @@ -175,7 +177,9 @@ abstract class CompilingStatement extends Statement { CompilationOutcomeDescriptor expectedResult = CompilationOutcomeDescriptor.forExpectedCompilationResult( - method.getAnnotation( ExpectedCompilationOutcome.class ) + method.getAnnotation( ExpectedCompilationOutcome.class ), + method.getAnnotation( ExpectedNote.ExpectedNotes.class ), + method.getAnnotation( ExpectedNote.class ) ); if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) { @@ -192,6 +196,7 @@ abstract class CompilingStatement extends Statement { } assertDiagnostics( actualResult.getDiagnostics(), expectedResult.getDiagnostics() ); + assertNotes( actualResult.getNotes(), expectedResult.getNotes() ); if ( runCheckstyle ) { assertCheckstyleRules(); @@ -239,6 +244,30 @@ abstract class CompilingStatement extends Statement { return files; } + private void assertNotes(List actualNotes, List expectedNotes) { + List expectedNotesRemaining = new ArrayList<>( expectedNotes ); + Iterator expectedNotesIterator = expectedNotesRemaining.iterator(); + if ( expectedNotesIterator.hasNext() ) { + String expectedNoteRegexp = expectedNotesIterator.next(); + for ( String actualNote : actualNotes ) { + if ( actualNote.matches( expectedNoteRegexp ) ) { + expectedNotesIterator.remove(); + if ( expectedNotesIterator.hasNext() ) { + expectedNoteRegexp = expectedNotesIterator.next(); + } + else { + break; + } + } + } + } + + assertThat( expectedNotesRemaining ) + .describedAs( "There are unmatched notes: " + + expectedNotesRemaining.stream().collect( Collectors.joining( LINE_SEPARATOR ) ).toString() ) + .isEmpty(); + } + private void assertDiagnostics(List actualDiagnostics, List expectedDiagnostics) {