#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
This commit is contained in:
Sjaak Derksen 2019-03-17 16:45:22 +01:00 committed by GitHub
parent bc010a52dc
commit b53741d960
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 812 additions and 25 deletions

View File

@ -223,6 +223,8 @@ When invoking javac directly, these options are passed to the compiler in the fo
<version>${org.mapstruct.version}</version> <version>${org.mapstruct.version}</version>
</path> </path>
</annotationProcessorPaths> </annotationProcessorPaths>
<!-- due to problem in maven-compiler-plugin, for verbose mode add showWarnings -->
<showWarnings>true</showWarnings>
<compilerArgs> <compilerArgs>
<compilerArg> <compilerArg>
-Amapstruct.suppressGeneratorTimestamp=true -Amapstruct.suppressGeneratorTimestamp=true
@ -230,6 +232,9 @@ When invoking javac directly, these options are passed to the compiler in the fo
<compilerArg> <compilerArg>
-Amapstruct.suppressGeneratorVersionInfoComment=true -Amapstruct.suppressGeneratorVersionInfoComment=true
</compilerArg> </compilerArg>
<compilerArg>
-Amapstruct.verbose=true
</compilerArg>
</compilerArgs> </compilerArgs>
</configuration> </configuration>
</plugin> </plugin>
@ -246,7 +251,8 @@ When invoking javac directly, these options are passed to the compiler in the fo
compileJava { compileJava {
options.compilerArgs = [ options.compilerArgs = [
'-Amapstruct.suppressGeneratorTimestamp=true', '-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. |If set to `true`, the creation of a time stamp in the `@Generated` annotation in the generated mapper classes is suppressed.
|`false` |`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. |`mapstruct.
suppressGeneratorVersionInfoComment` 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. |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.

View File

@ -82,7 +82,8 @@ import static javax.lang.model.element.ElementKind.CLASS;
MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP,
MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT,
MappingProcessor.UNMAPPED_TARGET_POLICY, MappingProcessor.UNMAPPED_TARGET_POLICY,
MappingProcessor.DEFAULT_COMPONENT_MODEL MappingProcessor.DEFAULT_COMPONENT_MODEL,
MappingProcessor.VERBOSE
}) })
public class MappingProcessor extends AbstractProcessor { 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 UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy";
protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel";
protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile";
protected static final String VERBOSE = "mapstruct.verbose";
private Options options; private Options options;
@ -120,7 +122,9 @@ public class MappingProcessor extends AbstractProcessor {
options = createOptions(); options = createOptions();
annotationProcessorContext = new AnnotationProcessorContext( annotationProcessorContext = new AnnotationProcessorContext(
processingEnv.getElementUtils(), 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 ) ), Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ),
unmappedTargetPolicy != null ? ReportingPolicyPrism.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, unmappedTargetPolicy != null ? ReportingPolicyPrism.valueOf( unmappedTargetPolicy.toUpperCase() ) : null,
processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), 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 ); processMapperTypeElement( context, mapperElement );
} }
catch ( TypeHierarchyErroneousException thie ) { catch ( TypeHierarchyErroneousException thie ) {
if ( options.isVerbose() ) {
processingEnv.getMessager().printMessage(
Kind.NOTE, "MapStruct: referred types not available (yet), deferring mapper: "
+ mapperElement );
}
deferredMappers.add( mapperElement ); deferredMappers.add( mapperElement );
} }
catch ( Throwable t ) { catch ( Throwable t ) {
@ -242,7 +252,7 @@ public class MappingProcessor extends AbstractProcessor {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
thrown.printStackTrace( new PrintWriter( sw ) ); thrown.printStackTrace( new PrintWriter( sw ) );
String reportableStacktrace = sw.toString().replace( System.getProperty( "line.separator" ), " " ); String reportableStacktrace = sw.toString().replace( System.lineSeparator( ), " " );
processingEnv.getMessager().printMessage( processingEnv.getMessager().printMessage(
Kind.ERROR, "Internal error in the mapping processor: " + reportableStacktrace, element ); Kind.ERROR, "Internal error in the mapping processor: " + reportableStacktrace, element );

View File

@ -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.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
/** /**
@ -94,6 +95,12 @@ public abstract class ContainerMappingMethodBuilder<B extends ContainerMappingMe
if ( assignment == null ) { if ( assignment == null ) {
assignment = forgeMapping( sourceRHS, sourceElementType, targetElementType ); assignment = forgeMapping( sourceRHS, sourceElementType, targetElementType );
if ( assignment != null ) {
ctx.getMessager().note( 2, Message.ITERABLEMAPPING_CREATE_ELEMENT_NOTE, assignment );
}
}
else {
ctx.getMessager().note( 2, Message.ITERABLEMAPPING_SELECT_ELEMENT_NOTE, assignment );
} }
if ( assignment == null ) { if ( assignment == null ) {

View File

@ -23,6 +23,7 @@ import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
/** /**
@ -98,8 +99,13 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
if ( keyAssignment == null ) { if ( keyAssignment == null ) {
keyAssignment = forgeMapping( keySourceRHS, keySourceType, keyTargetType ); keyAssignment = forgeMapping( keySourceRHS, keySourceType, keyTargetType );
if ( keyAssignment != null ) {
ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_KEY_NOTE, keyAssignment );
}
}
else {
ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_KEY_NOTE, keyAssignment );
} }
if ( keyAssignment == null ) { if ( keyAssignment == null ) {
if ( method instanceof ForgedMethod ) { if ( method instanceof ForgedMethod ) {
@ -150,6 +156,12 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
if ( valueAssignment == null ) { if ( valueAssignment == null ) {
valueAssignment = forgeMapping( valueSourceRHS, valueSourceType, valueTargetType ); valueAssignment = forgeMapping( valueSourceRHS, valueSourceType, valueTargetType );
if ( valueAssignment != null ) {
ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_VALUE_NOTE, valueAssignment );
}
}
else {
ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_VALUE_NOTE, valueAssignment );
} }
if ( valueAssignment == null ) { if ( valueAssignment == null ) {

View File

@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.ConversionContext;
@ -19,6 +20,7 @@ import org.mapstruct.ap.internal.model.common.ParameterBinding;
import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod;
import org.mapstruct.ap.internal.util.Strings;
/** /**
* Represents a reference to another method, e.g. used to map a bean property from source to target type or to * Represents a reference to another method, e.g. used to map a bean property from source to target type or to
@ -348,4 +350,15 @@ public class MethodReference extends ModelElement implements Assignment {
return new MethodReference( methodName, null, false ); return new MethodReference( methodName, null, false );
} }
@Override
public String toString() {
String mapper = declaringMapper != null ? declaringMapper.getType().getName().toString() : "";
String argument = getAssignment() != null ? getAssignment().toString() : getSourceReference();
String returnTypeAsString = returnType != null ? returnType.toString() : "";
List<String> arguments = sourceParameters.stream()
.map( p -> p.isMappingContext() || p.isMappingTarget() || p.isTargetType() ? p.getName() : argument )
.collect( Collectors.toList() );
return returnTypeAsString + " " + mapper + "#" + name + "(" + Strings.join( arguments, "," ) + ")";
}
} }

View File

@ -295,6 +295,9 @@ public class PropertyMapping extends ModelElement {
// handle source // handle source
this.rightHandSide = getSourceRHS( sourceReference ); this.rightHandSide = getSourceRHS( sourceReference );
ctx.getMessager().note( 2, Message.PROPERTYMAPPING_MAPPING_NOTE, rightHandSide, targetWriteAccessor );
rightHandSide.setUseElementAsSourceTypeForMatching( rightHandSide.setUseElementAsSourceTypeForMatching(
targetWriteAccessorType == TargetWriteAccessorType.ADDER ); targetWriteAccessorType == TargetWriteAccessorType.ADDER );
@ -339,6 +342,12 @@ public class PropertyMapping extends ModelElement {
else { else {
assignment = forgeMapping( rightHandSide ); 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 ) { if ( assignment != null ) {

View File

@ -140,4 +140,10 @@ public class TypeConversion extends ModelElement implements Assignment {
public boolean isCallingUpdateMethod() { public boolean isCallingUpdateMethod() {
return false; return false;
} }
@Override
public String toString() {
String argument = getAssignment() != null ? getAssignment().toString() : getSourceReference();
return openExpression + argument + closeExpression;
}
} }

View File

@ -19,15 +19,17 @@ public class Options {
private final ReportingPolicyPrism unmappedTargetPolicy; private final ReportingPolicyPrism unmappedTargetPolicy;
private final boolean alwaysGenerateSpi; private final boolean alwaysGenerateSpi;
private final String defaultComponentModel; private final String defaultComponentModel;
private final boolean verbose;
public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment,
ReportingPolicyPrism unmappedTargetPolicy, ReportingPolicyPrism unmappedTargetPolicy,
String defaultComponentModel, boolean alwaysGenerateSpi) { String defaultComponentModel, boolean alwaysGenerateSpi, boolean verbose) {
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; this.suppressGeneratorVersionComment = suppressGeneratorVersionComment;
this.unmappedTargetPolicy = unmappedTargetPolicy; this.unmappedTargetPolicy = unmappedTargetPolicy;
this.defaultComponentModel = defaultComponentModel; this.defaultComponentModel = defaultComponentModel;
this.alwaysGenerateSpi = alwaysGenerateSpi; this.alwaysGenerateSpi = alwaysGenerateSpi;
this.verbose = verbose;
} }
public boolean isSuppressGeneratorTimestamp() { public boolean isSuppressGeneratorTimestamp() {
@ -49,4 +51,8 @@ public class Options {
public boolean isAlwaysGenerateSpi() { public boolean isAlwaysGenerateSpi() {
return alwaysGenerateSpi; return alwaysGenerateSpi;
} }
public boolean isVerbose() {
return verbose;
}
} }

View File

@ -6,6 +6,7 @@
package org.mapstruct.ap.internal.processor; package org.mapstruct.ap.internal.processor;
import java.util.Map; import java.util.Map;
import java.util.stream.IntStream;
import javax.annotation.processing.Filer; import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager; import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
@ -45,7 +46,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
RoundContext roundContext, Map<String, String> notToBeImported) { RoundContext roundContext, Map<String, String> notToBeImported) {
this.processingEnvironment = processingEnvironment; this.processingEnvironment = processingEnvironment;
this.messager = new DelegatingMessager( processingEnvironment.getMessager() ); this.messager = new DelegatingMessager( processingEnvironment.getMessager(), options.isVerbose() );
this.accessorNaming = roundContext.getAnnotationProcessorContext().getAccessorNaming(); this.accessorNaming = roundContext.getAnnotationProcessorContext().getAccessorNaming();
this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment );
this.delegatingTypes = new TypesDecorator( processingEnvironment, versionInformation ); this.delegatingTypes = new TypesDecorator( processingEnvironment, versionInformation );
@ -108,9 +109,11 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
private final Messager delegate; private final Messager delegate;
private boolean isErroneous = false; private boolean isErroneous = false;
private final boolean verbose;
DelegatingMessager(Messager delegate) { DelegatingMessager(Messager delegate, boolean verbose) {
this.delegate = delegate; this.delegate = delegate;
this.verbose = verbose;
} }
@Override @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() { public boolean isErroneous() {
return isErroneous; return isErroneous;
} }

View File

@ -38,9 +38,9 @@ import org.mapstruct.ap.internal.model.MappingMethod;
import org.mapstruct.ap.internal.model.StreamMappingMethod; import org.mapstruct.ap.internal.model.StreamMappingMethod;
import org.mapstruct.ap.internal.model.SupportingConstructorFragment; import org.mapstruct.ap.internal.model.SupportingConstructorFragment;
import org.mapstruct.ap.internal.model.ValueMappingMethod; 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.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory; 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.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
@ -286,6 +286,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
boolean hasFactoryMethod = false; boolean hasFactoryMethod = false;
if ( method.isIterableMapping() ) { if ( method.isIterableMapping() ) {
this.messager.note( 1, Message.ITERABLEMAPPING_CREATE_NOTE, method );
IterableMappingMethod iterableMappingMethod = createWithElementMappingMethod( IterableMappingMethod iterableMappingMethod = createWithElementMappingMethod(
method, method,
mappingOptions, mappingOptions,
@ -313,6 +314,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
nullValueMappingStrategy = mappingOptions.getMapMapping().getNullValueMappingStrategy(); nullValueMappingStrategy = mappingOptions.getMapMapping().getNullValueMappingStrategy();
} }
this.messager.note( 1, Message.MAPMAPPING_CREATE_NOTE, method );
MapMappingMethod mapMappingMethod = builder MapMappingMethod mapMappingMethod = builder
.mappingContext( mappingContext ) .mappingContext( mappingContext )
.method( method ) .method( method )
@ -328,6 +330,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
} }
else if ( method.isValueMapping() ) { else if ( method.isValueMapping() ) {
// prefer value mappings over enum mapping // prefer value mappings over enum mapping
this.messager.note( 1, Message.VALUEMAPPING_CREATE_NOTE, method );
ValueMappingMethod valueMappingMethod = new ValueMappingMethod.Builder() ValueMappingMethod valueMappingMethod = new ValueMappingMethod.Builder()
.mappingContext( mappingContext ) .mappingContext( mappingContext )
.method( method ) .method( method )
@ -352,6 +355,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
} }
} }
else if ( method.isStreamMapping() ) { else if ( method.isStreamMapping() ) {
this.messager.note( 1, Message.STREAMMAPPING_CREATE_NOTE, method );
StreamMappingMethod streamMappingMethod = createWithElementMappingMethod( StreamMappingMethod streamMappingMethod = createWithElementMappingMethod(
method, method,
mappingOptions, mappingOptions,
@ -364,8 +368,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
mappingMethods.add( streamMappingMethod ); mappingMethods.add( streamMappingMethod );
} }
else { else {
this.messager.note( 1, Message.BEANMAPPING_CREATE_NOTE, method );
BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder(); BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder();
BeanMappingMethod beanMappingMethod = builder BeanMappingMethod beanMappingMethod = builder
.mappingContext( mappingContext ) .mappingContext( mappingContext )

View File

@ -72,8 +72,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
this.typeUtils = context.getTypeUtils(); this.typeUtils = context.getTypeUtils();
this.elementUtils = context.getElementUtils(); this.elementUtils = context.getElementUtils();
this.messager.note( 0, Message.PROCESSING_NOTE, mapperTypeElement );
MapperConfiguration mapperConfig = MapperConfiguration.getInstanceOn( mapperTypeElement ); MapperConfiguration mapperConfig = MapperConfiguration.getInstanceOn( mapperTypeElement );
if ( mapperConfig != null ) {
this.messager.note( 0, Message.CONFIG_NOTE, mapperConfig.getClass().getName() );
}
if ( !mapperConfig.isValid() ) { if ( !mapperConfig.isValid() ) {
throw new AnnotationProcessingException( throw new AnnotationProcessingException(
"Couldn't retrieve @Mapper annotation", "Couldn't retrieve @Mapper annotation",

View File

@ -10,8 +10,10 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import javax.annotation.processing.Messager;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor;
@ -39,12 +41,16 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
private AccessorNamingUtils accessorNaming; private AccessorNamingUtils accessorNaming;
private Elements elementUtils; private Elements elementUtils;
private Types typeUtils; private Types typeUtils;
private Messager messager;
private boolean verbose;
public AnnotationProcessorContext(Elements elementUtils, Types typeUtils) { public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean verbose) {
astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList( astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList(
findAstModifyingAnnotationProcessors() ); findAstModifyingAnnotationProcessors() );
this.elementUtils = elementUtils; this.elementUtils = elementUtils;
this.typeUtils = typeUtils; this.typeUtils = typeUtils;
this.messager = messager;
this.verbose = verbose;
} }
/** /**
@ -64,10 +70,16 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
if ( elementUtils.getTypeElement( ImmutablesConstants.IMMUTABLE_FQN ) != null ) { if ( elementUtils.getTypeElement( ImmutablesConstants.IMMUTABLE_FQN ) != null ) {
defaultAccessorNamingStrategy = new ImmutablesAccessorNamingStrategy(); defaultAccessorNamingStrategy = new ImmutablesAccessorNamingStrategy();
defaultBuilderProvider = new ImmutablesBuilderProvider(); defaultBuilderProvider = new ImmutablesBuilderProvider();
if ( verbose ) {
messager.printMessage( Diagnostic.Kind.NOTE, "MapStruct: Immutables found on classpath" );
}
} }
else if ( elementUtils.getTypeElement( FreeBuilderConstants.FREE_BUILDER_FQN ) != null ) { else if ( elementUtils.getTypeElement( FreeBuilderConstants.FREE_BUILDER_FQN ) != null ) {
defaultAccessorNamingStrategy = new FreeBuilderAccessorNamingStrategy(); defaultAccessorNamingStrategy = new FreeBuilderAccessorNamingStrategy();
defaultBuilderProvider = new DefaultBuilderProvider(); defaultBuilderProvider = new DefaultBuilderProvider();
if ( verbose ) {
messager.printMessage( Diagnostic.Kind.NOTE, "MapStruct: Freebuilder found on classpath" );
}
} }
else { else {
defaultAccessorNamingStrategy = new DefaultAccessorNamingStrategy(); defaultAccessorNamingStrategy = new DefaultAccessorNamingStrategy();
@ -75,8 +87,21 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
} }
this.accessorNamingStrategy = Services.get( AccessorNamingStrategy.class, defaultAccessorNamingStrategy ); this.accessorNamingStrategy = Services.get( AccessorNamingStrategy.class, defaultAccessorNamingStrategy );
this.accessorNamingStrategy.init( this ); this.accessorNamingStrategy.init( this );
if ( verbose ) {
messager.printMessage(
Diagnostic.Kind.NOTE,
"MapStruct: Using accessor naming strategy: "
+ this.accessorNamingStrategy.getClass().getCanonicalName()
);
}
this.builderProvider = Services.get( BuilderProvider.class, defaultBuilderProvider ); this.builderProvider = Services.get( BuilderProvider.class, defaultBuilderProvider );
this.builderProvider.init( this ); this.builderProvider.init( this );
if ( verbose ) {
messager.printMessage(
Diagnostic.Kind.NOTE,
"MapStruct: Using builder provider: " + this.builderProvider.getClass().getCanonicalName()
);
}
this.accessorNaming = new AccessorNamingUtils( this.accessorNamingStrategy ); this.accessorNaming = new AccessorNamingUtils( this.accessorNamingStrategy );
this.initialized = true; this.initialized = true;
} }

View File

@ -67,4 +67,12 @@ public interface FormattingMessager {
AnnotationValue v, AnnotationValue v,
Message msg, Message msg,
Object... args); Object... args);
/**
* Just log as plain note
* @param level nesting level
* @param log the log message
* @param args the arguments
*/
void note(int level, Message log, Object... args);
} }

View File

@ -15,6 +15,10 @@ import javax.tools.Diagnostic;
public enum Message { public enum Message {
// CHECKSTYLE:OFF // CHECKSTYLE:OFF
PROCESSING_NOTE( "processing: %s.", Diagnostic.Kind.NOTE ),
CONFIG_NOTE( "applying mapper configuration: %s.", Diagnostic.Kind.NOTE ),
BEANMAPPING_CREATE_NOTE( "creating bean mapping method implementation for %s.", Diagnostic.Kind.NOTE ),
BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'nullValuePropertyMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ), BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'nullValuePropertyMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ),
BEANMAPPING_NOT_ASSIGNABLE( "%s not assignable to: %s." ), BEANMAPPING_NOT_ASSIGNABLE( "%s not assignable to: %s." ),
BEANMAPPING_ABSTRACT( "The result type %s may not be an abstract class nor interface." ), BEANMAPPING_ABSTRACT( "The result type %s may not be an abstract class nor interface." ),
@ -31,6 +35,9 @@ public enum Message {
BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ),
BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ),
PROPERTYMAPPING_MAPPING_NOTE( "mapping property: %s to: %s.", Diagnostic.Kind.NOTE ),
PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ),
PROPERTYMAPPING_SELECT_NOTE( "selecting property mapping: %s.", Diagnostic.Kind.NOTE ),
PROPERTYMAPPING_MAPPING_NOT_FOUND( "Can't map %s to \"%s %s\". Consider to declare/implement a mapping method: \"%s map(%s value)\"." ), PROPERTYMAPPING_MAPPING_NOT_FOUND( "Can't map %s to \"%s %s\". Consider to declare/implement a mapping method: \"%s map(%s value)\"." ),
PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND( "Can't map %s to %s. Consider to implement a mapping method: \"%s map(%s value)\"." ), PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND( "Can't map %s to %s. Consider to implement a mapping method: \"%s map(%s value)\"." ),
PROPERTYMAPPING_DUPLICATE_TARGETS( "Target property \"%s\" must not be mapped more than once." ), PROPERTYMAPPING_DUPLICATE_TARGETS( "Target property \"%s\" must not be mapped more than once." ),
@ -67,10 +74,19 @@ public enum Message {
CONSTANTMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE( "No read accessor found for property \"%s\" in target type." ), CONSTANTMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE( "No read accessor found for property \"%s\" in target type." ),
CONSTANTMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s for property \"%s\"." ), CONSTANTMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s for property \"%s\"." ),
MAPMAPPING_CREATE_NOTE( "creating map mapping method implementation for %s.", Diagnostic.Kind.NOTE ),
MAPMAPPING_KEY_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source key type to target key type." ), MAPMAPPING_KEY_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source key type to target key type." ),
MAPMAPPING_VALUE_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source value type to target value type." ), MAPMAPPING_VALUE_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source value type to target value type." ),
MAPMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', 'valueDateFormat', 'valueQualfiedBy' and 'valueTargetType' are all undefined in @MapMapping, define at least one of them." ), MAPMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', 'valueDateFormat', 'valueQualfiedBy' and 'valueTargetType' are all undefined in @MapMapping, define at least one of them." ),
MAPMAPPING_SELECT_KEY_NOTE( "selecting key mapping: %s.", Diagnostic.Kind.NOTE ),
MAPMAPPING_SELECT_VALUE_NOTE( "selecting value mapping: %s.", Diagnostic.Kind.NOTE ),
MAPMAPPING_CREATE_KEY_NOTE( "creating key mapping: %s.", Diagnostic.Kind.NOTE ),
MAPMAPPING_CREATE_VALUE_NOTE( "creating value mapping: %s.", Diagnostic.Kind.NOTE ),
STREAMMAPPING_CREATE_NOTE( "creating stream mapping method implementation for %s.", Diagnostic.Kind.NOTE ),
ITERABLEMAPPING_CREATE_NOTE( "creating iterable mapping method implementation for %s.", Diagnostic.Kind.NOTE ),
ITERABLEMAPPING_SELECT_ELEMENT_NOTE( "selecting element mapping: %s.", Diagnostic.Kind.NOTE ),
ITERABLEMAPPING_CREATE_ELEMENT_NOTE( "creating element mapping: %s.", Diagnostic.Kind.NOTE ),
ITERABLEMAPPING_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source element type into target element type." ), ITERABLEMAPPING_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source element type into target element type." ),
ITERABLEMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are undefined in @IterableMapping, define at least one of them." ), ITERABLEMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are undefined in @IterableMapping, define at least one of them." ),
@ -128,6 +144,7 @@ public enum Message {
INHERITINVERSECONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH( "More than one configuration prototype method is applicable. Use @InheritInverseConfiguration to select one of them explicitly: %s." ), INHERITINVERSECONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH( "More than one configuration prototype method is applicable. Use @InheritInverseConfiguration to select one of them explicitly: %s." ),
INHERITCONFIGURATION_CYCLE( "Cycle detected while evaluating inherited configurations. Inheritance path: %s" ), INHERITCONFIGURATION_CYCLE( "Cycle detected while evaluating inherited configurations. Inheritance path: %s" ),
VALUEMAPPING_CREATE_NOTE( "creating value mapping method implementation for %s.", Diagnostic.Kind.NOTE ),
VALUEMAPPING_DUPLICATE_SOURCE( "Source value mapping: \"%s\" cannot be mapped more than once." ), VALUEMAPPING_DUPLICATE_SOURCE( "Source value mapping: \"%s\" cannot be mapped more than once." ),
VALUEMAPPING_ANY_AREADY_DEFINED( "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" can only be used once." ), VALUEMAPPING_ANY_AREADY_DEFINED( "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" 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." ), 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." ),

View File

@ -28,4 +28,9 @@ public class ExecutableElementAccessor extends AbstractAccessor<ExecutableElemen
public ExecutableElement getExecutable() { public ExecutableElement getExecutable() {
return element; return element;
} }
@Override
public String toString() {
return element.toString();
}
} }

View File

@ -29,4 +29,9 @@ public class VariableElementAccessor extends AbstractAccessor<VariableElement> {
public ExecutableElement getExecutable() { public ExecutableElement getExecutable() {
return null; return null;
} }
@Override
public String toString() {
return element.toString();
}
} }

View File

@ -37,7 +37,7 @@ class IndentationCorrectingWriter extends Writer {
* Set to true to enable output of written characters on the console. * Set to true to enable output of written characters on the console.
*/ */
private static final boolean DEBUG = false; 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 static final boolean IS_WINDOWS = System.getProperty( "os.name" ).startsWith( "Windows" );
private State currentState = State.START_OF_LINE; private State currentState = State.START_OF_LINE;

View File

@ -280,4 +280,5 @@ public class DefaultBuilderProvider implements BuilderProvider {
protected boolean shouldIgnore(TypeElement typeElement) { protected boolean shouldIgnore(TypeElement typeElement) {
return typeElement == null || JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName() ).matches(); return typeElement == null || JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName() ).matches();
} }
} }

View File

@ -35,4 +35,5 @@ public class FreeBuilderAccessorNamingStrategy extends DefaultAccessorNamingStra
// with set // with set
return false; return false;
} }
} }

View File

@ -23,4 +23,5 @@ public class ImmutablesAccessorNamingStrategy extends DefaultAccessorNamingStrat
protected boolean isFluentSetter(ExecutableElement method) { protected boolean isFluentSetter(ExecutableElement method) {
return super.isFluentSetter( method ) && !method.getSimpleName().toString().equals( "from" ); return super.isFluentSetter( method ) && !method.getSimpleName().toString().equals( "from" );
} }
} }

View File

@ -83,4 +83,5 @@ public class ImmutablesBuilderProvider extends DefaultBuilderProvider {
builderQualifiedName.append( "Immutable" ).append( typeElement.getSimpleName() ); builderQualifiedName.append( "Immutable" ).append( typeElement.getSimpleName() );
return elementUtils.getTypeElement( builderQualifiedName ); return elementUtils.getTypeElement( builderQualifiedName );
} }
} }

View File

@ -23,5 +23,6 @@ public class NoOpBuilderProvider implements BuilderProvider {
public BuilderInfo findBuilderInfo(TypeMirror type) { public BuilderInfo findBuilderInfo(TypeMirror type) {
return null; return null;
} }
} }
// end::documentation[] // end::documentation[]

View File

@ -153,6 +153,11 @@ public class DefaultConversionContextTest {
lastKindPrinted = msg.getDiagnosticKind(); lastKindPrinted = msg.getDiagnosticKind();
} }
@Override
public void note(int level, Message msg, Object... args) {
throw new UnsupportedOperationException( "Should not be called" );
}
public Diagnostic.Kind getLastKindPrinted() { public Diagnostic.Kind getLastKindPrinted() {
return lastKindPrinted; return lastKindPrinted;
} }

View File

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

View File

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

View File

@ -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<TargetElement> map(List<SourceElement> 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;
}
}
}

View File

@ -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<TargetKey, TargetValue> map(Map<SourceKey, SourceValue> 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;
}
}
}

View File

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

View File

@ -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<TargetElement> map(List<SourceElement> source);
default TargetElement map(SourceElement sourceKey) {
return null;
}
class SourceElement {
}
class TargetElement {
}
}

View File

@ -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<TargetKey, TargetValue> map(Map<SourceKey, SourceValue> source);
default TargetKey map(SourceKey sourceKey) {
return null;
}
default TargetValue map(SourceValue sourceValue) {
return null;
}
class SourceKey {
}
class SourceValue {
}
class TargetKey {
}
class TargetValue {
}
}

View File

@ -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<TargetElement> map(Stream<SourceElement> source);
default TargetElement map(SourceElement sourceKey) {
return null;
}
class SourceElement {
}
class TargetElement {
}
}

View File

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

View File

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

View File

@ -6,6 +6,7 @@
package org.mapstruct.ap.testutil; package org.mapstruct.ap.testutil;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@ -17,6 +18,7 @@ import java.lang.annotation.Target;
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD }) @Target({ ElementType.TYPE, ElementType.METHOD })
@Repeatable( WithServiceImplementations.class )
public @interface WithServiceImplementation { public @interface WithServiceImplementation {
/** /**
* @return The service implementation class that is to be made available during the annotation processing. * @return The service implementation class that is to be made available during the annotation processing.

View File

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

View File

@ -8,6 +8,8 @@ package org.mapstruct.ap.testutil.compilation.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
@ -17,6 +19,7 @@ import org.codehaus.plexus.compiler.CompilerMessage;
import org.codehaus.plexus.compiler.CompilerResult; import org.codehaus.plexus.compiler.CompilerResult;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote;
/** /**
* Represents the outcome of a compilation. * Represents the outcome of a compilation.
@ -25,21 +28,37 @@ import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutco
*/ */
public class CompilationOutcomeDescriptor { public class CompilationOutcomeDescriptor {
private static final String LINE_SEPARATOR = System.lineSeparator( );
private CompilationResult compilationResult; private CompilationResult compilationResult;
private List<DiagnosticDescriptor> diagnostics; private List<DiagnosticDescriptor> diagnostics;
private List<String> notes;
private CompilationOutcomeDescriptor(CompilationResult compilationResult, private CompilationOutcomeDescriptor(CompilationResult compilationResult,
List<DiagnosticDescriptor> diagnostics) { List<DiagnosticDescriptor> diagnostics,
List<String> notes) {
this.compilationResult = compilationResult; this.compilationResult = compilationResult;
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
this.notes = notes;
} }
public static CompilationOutcomeDescriptor forExpectedCompilationResult( public static CompilationOutcomeDescriptor forExpectedCompilationResult(
ExpectedCompilationOutcome expectedCompilationResult) { ExpectedCompilationOutcome expectedCompilationResult, ExpectedNote.ExpectedNotes expectedNotes,
ExpectedNote expectedNote) {
List<String> 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 ) { if ( expectedCompilationResult == null ) {
return new CompilationOutcomeDescriptor( return new CompilationOutcomeDescriptor(
CompilationResult.SUCCEEDED, CompilationResult.SUCCEEDED,
Collections.<DiagnosticDescriptor>emptyList() Collections.<DiagnosticDescriptor>emptyList(),
notes
); );
} }
else { else {
@ -48,8 +67,7 @@ public class CompilationOutcomeDescriptor {
expectedCompilationResult.diagnostics() ) { expectedCompilationResult.diagnostics() ) {
diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) ); diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) );
} }
return new CompilationOutcomeDescriptor( expectedCompilationResult.value(), diagnosticDescriptors, notes );
return new CompilationOutcomeDescriptor( expectedCompilationResult.value(), diagnosticDescriptors );
} }
} }
@ -57,31 +75,34 @@ public class CompilationOutcomeDescriptor {
List<Diagnostic<? extends JavaFileObject>> diagnostics) { List<Diagnostic<? extends JavaFileObject>> diagnostics) {
CompilationResult compilationResult = CompilationResult compilationResult =
compilationSuccessful ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; compilationSuccessful ? CompilationResult.SUCCEEDED : CompilationResult.FAILED;
List<String> notes = new ArrayList<>();
List<DiagnosticDescriptor> diagnosticDescriptors = new ArrayList<DiagnosticDescriptor>(); List<DiagnosticDescriptor> diagnosticDescriptors = new ArrayList<DiagnosticDescriptor>();
for ( Diagnostic<? extends JavaFileObject> diagnostic : diagnostics ) { for ( Diagnostic<? extends JavaFileObject> diagnostic : diagnostics ) {
//ignore notes created by the compiler //ignore notes created by the compiler
if ( diagnostic.getKind() != Kind.NOTE ) { if ( diagnostic.getKind() != Kind.NOTE ) {
diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( sourceDir, diagnostic ) ); 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) { public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerResult compilerResult) {
CompilationResult compilationResult = CompilationResult compilationResult =
compilerResult.isSuccess() ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; compilerResult.isSuccess() ? CompilationResult.SUCCEEDED : CompilationResult.FAILED;
List<DiagnosticDescriptor> diagnosticDescriptors = new ArrayList<DiagnosticDescriptor>(); List<DiagnosticDescriptor> diagnosticDescriptors = new ArrayList<DiagnosticDescriptor>();
for ( CompilerMessage message : compilerResult.getCompilerMessages() ) { for ( CompilerMessage message : compilerResult.getCompilerMessages() ) {
if ( message.getKind() != CompilerMessage.Kind.NOTE ) { if ( message.getKind() != CompilerMessage.Kind.NOTE ) {
diagnosticDescriptors.add( DiagnosticDescriptor.forCompilerMessage( sourceDir, message ) ); 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() { public CompilationResult getCompilationResult() {
@ -92,6 +113,10 @@ public class CompilationOutcomeDescriptor {
return diagnostics; return diagnostics;
} }
public List<String> getNotes() {
return notes;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement; 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.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.DisableCheckstyle; import org.mapstruct.ap.testutil.compilation.annotation.DisableCheckstyle;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; 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.ProcessorOption;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOptions; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOptions;
import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; 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 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(); private static final DiagnosticDescriptorComparator COMPARATOR = new DiagnosticDescriptorComparator();
@ -175,7 +177,9 @@ abstract class CompilingStatement extends Statement {
CompilationOutcomeDescriptor expectedResult = CompilationOutcomeDescriptor expectedResult =
CompilationOutcomeDescriptor.forExpectedCompilationResult( CompilationOutcomeDescriptor.forExpectedCompilationResult(
method.getAnnotation( ExpectedCompilationOutcome.class ) method.getAnnotation( ExpectedCompilationOutcome.class ),
method.getAnnotation( ExpectedNote.ExpectedNotes.class ),
method.getAnnotation( ExpectedNote.class )
); );
if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) { if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) {
@ -192,6 +196,7 @@ abstract class CompilingStatement extends Statement {
} }
assertDiagnostics( actualResult.getDiagnostics(), expectedResult.getDiagnostics() ); assertDiagnostics( actualResult.getDiagnostics(), expectedResult.getDiagnostics() );
assertNotes( actualResult.getNotes(), expectedResult.getNotes() );
if ( runCheckstyle ) { if ( runCheckstyle ) {
assertCheckstyleRules(); assertCheckstyleRules();
@ -239,6 +244,30 @@ abstract class CompilingStatement extends Statement {
return files; return files;
} }
private void assertNotes(List<String> actualNotes, List<String> expectedNotes) {
List<String> expectedNotesRemaining = new ArrayList<>( expectedNotes );
Iterator<String> 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<DiagnosticDescriptor> actualDiagnostics, private void assertDiagnostics(List<DiagnosticDescriptor> actualDiagnostics,
List<DiagnosticDescriptor> expectedDiagnostics) { List<DiagnosticDescriptor> expectedDiagnostics) {