Skip to content

Commit

Permalink
mapstruct#1154 Add SPI for excluding types/elements from automatic su…
Browse files Browse the repository at this point in the history
…b-mapping generation.

Default implementation of SPI ignores types in the java/javax packages
  • Loading branch information
filiphr committed Apr 5, 2017
1 parent a40738f commit 7fafa6f
Show file tree
Hide file tree
Showing 17 changed files with 547 additions and 17 deletions.
Expand Up @@ -50,7 +50,11 @@ public B method(Method sourceMethod) {
return myself;
}

boolean isDisableSubMappingMethodsGeneration() {
boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType) {
return !isDisableSubMappingMethodsGeneration() && ctx.canGenerateAutoSubMappingBetween( sourceType, targetType );
}

private boolean isDisableSubMappingMethodsGeneration() {
MapperConfiguration configuration = MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() );
return configuration.isDisableSubMappingMethodsGeneration();
}
Expand Down Expand Up @@ -123,11 +127,11 @@ private Assignment createAssignment(SourceRHS source, ForgedMethod methodRef) {
*
* @param method the method that should be mapped
* @param sourceErrorMessagePart the error message part for the source
* @param sourceRHS the {@link SourceRHS}
* @param sourceType the source type of the mapping
* @param targetType the type of the target mapping
* @param targetPropertyName the name of the target property
*/
void reportCannotCreateMapping(Method method, String sourceErrorMessagePart, SourceRHS sourceRHS, Type targetType,
void reportCannotCreateMapping(Method method, String sourceErrorMessagePart, Type sourceType, Type targetType,
String targetPropertyName) {
ctx.getMessager().printMessage(
method.getExecutable(),
Expand All @@ -136,7 +140,7 @@ void reportCannotCreateMapping(Method method, String sourceErrorMessagePart, Sou
targetType,
targetPropertyName,
targetType,
sourceRHS.getSourceType() /* original source type */
sourceType
);
}
}
Expand Up @@ -45,7 +45,7 @@ public AbstractMappingMethodBuilder(Class<B> selfType) {
protected abstract boolean shouldUsePropertyNamesInHistory();

Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targetType) {
if ( isDisableSubMappingMethodsGeneration() ) {
if ( !canGenerateAutoSubMappingBetween( sourceType, targetType ) ) {
return null;
}

Expand Down
Expand Up @@ -121,7 +121,7 @@ public final M build() {
reportCannotCreateMapping(
method,
String.format( "%s \"%s\"", sourceRHS.getSourceErrorMessagePart(), sourceRHS.getSourceType() ),
sourceRHS,
sourceRHS.getSourceType(),
targetElementType,
""
);
Expand Down
Expand Up @@ -124,7 +124,7 @@ public MapMappingMethod build() {
keySourceRHS.getSourceErrorMessagePart(),
keySourceRHS.getSourceType()
),
keySourceRHS,
keySourceRHS.getSourceType(),
keyTargetType,
""
);
Expand Down Expand Up @@ -174,7 +174,7 @@ public MapMappingMethod build() {
valueSourceRHS.getSourceErrorMessagePart(),
valueSourceRHS.getSourceType()
),
valueSourceRHS,
valueSourceRHS.getSourceType(),
valueTargetType,
""
);
Expand Down
Expand Up @@ -38,6 +38,9 @@
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Services;
import org.mapstruct.ap.spi.DefaultSubMappingExclusionProvider;
import org.mapstruct.ap.spi.SubMappingExclusionProvider;

/**
* This class provides the context for the builders.
Expand All @@ -54,6 +57,11 @@
*/
public class MappingBuilderContext {

private static final SubMappingExclusionProvider SUB_MAPPING_EXCLUSION_PROVIDER = Services.get(
SubMappingExclusionProvider.class,
new DefaultSubMappingExclusionProvider()
);

/**
* Resolves the most suitable way for mapping an element (property, iterable element etc.) from source to target.
* There are 2 basic types of mappings:
Expand Down Expand Up @@ -222,4 +230,18 @@ public Set<VirtualMappingMethod> getUsedVirtualMappings() {
return mappingResolver.getUsedVirtualMappings();
}

/**
* @param sourceType to be checked
* @param targetType to be checked
*
* @return {@code true} if MapStruct is allowed to try and generate an automatic sub-mapping between the
* source and target {@link Type}
*/
public boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType) {
return canGenerateAutoSubMappingFor( sourceType ) && canGenerateAutoSubMappingFor( targetType );
}

private boolean canGenerateAutoSubMappingFor(Type type) {
return type.getTypeElement() != null && !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getTypeElement() );
}
}
Expand Up @@ -302,13 +302,7 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get
}
}
else {
reportCannotCreateMapping(
method,
rightHandSide.getSourceErrorMessagePart(),
rightHandSide,
targetType,
targetPropertyName
);
reportCannotCreateMapping();
}

return new PropertyMapping(
Expand All @@ -323,6 +317,28 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get
);
}

private void reportCannotCreateMapping() {
if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) {
ForgedMethodHistory history = ( (ForgedMethod) method ).getHistory();
reportCannotCreateMapping(
method,
history.createSourcePropertyErrorMessage(),
history.getSourceType(),
history.getTargetType(),
history.createTargetPropertyName()
);
}
else {
reportCannotCreateMapping(
method,
rightHandSide.getSourceErrorMessagePart(),
rightHandSide.getSourceType(),
targetType,
targetPropertyName
);
}
}

private Assignment getDefaultValueAssignment( Assignment rhs ) {
if ( defaultValue != null
&& ( !rhs.getSourceType().isPrimitive() || rhs.getSourcePresenceCheckerReference() != null) ) {
Expand Down Expand Up @@ -633,11 +649,11 @@ private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS s
}

private Assignment forgeMapping(SourceRHS sourceRHS) {
if ( forgedNamedBased && isDisableSubMappingMethodsGeneration() ) {
Type sourceType = sourceRHS.getSourceType();
if ( forgedNamedBased && !canGenerateAutoSubMappingBetween( sourceType, targetType ) ) {
return null;
}

Type sourceType = sourceRHS.getSourceType();

//Fail fast. If we could not find the method by now, no need to try
if ( sourceType.isPrimitive() || targetType.isPrimitive() ) {
Expand Down
@@ -0,0 +1,46 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.spi;

import java.util.regex.Pattern;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;

/**
* The default implementation of the {@link SubMappingExclusionProvider} service provider interface.
*
* With the default implementation, MapStruct will exclude all classes under the under the {@code java} and {@code
* javax} package and will not try to create an automatic sub-mapping method for all classes. The only exception is
* the {@link java.util.Collection}, {@link java.util.Map} and {@link java.util.stream.Stream} types.
*
* @author Filip Hrisafov
*/
public class DefaultSubMappingExclusionProvider implements SubMappingExclusionProvider {
private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" );

@Override
public boolean isExcluded(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
return name.length() != 0 && isFullyQualifiedNameExcluded( name );
}

protected boolean isFullyQualifiedNameExcluded(Name name) {
return JAVA_JAVAX_PACKAGE.matcher( name ).matches();
}
}
@@ -0,0 +1,40 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.spi;

import javax.lang.model.element.TypeElement;

/**
* A service provider interface that is used to control if MapStruct is allowed to generate automatic sub-mapping for
* a given {@link TypeElement}.
*
* @author Filip Hrisafov
*/
public interface SubMappingExclusionProvider {

/**
* Checks if the MapStruct should not generate an automatic sub-mapping for the provided {@link TypeElement}, i.e.
* MapStruct will not try to descent into this class and won't try to automatically map it with some other type.
*
* @param typeElement That needs to be checked
*
* @return {@code true} if MapStruct should exclude the provided {@link TypeElement} from an automatic sub-mapping
*/
boolean isExcluded(TypeElement typeElement);
}
@@ -0,0 +1,30 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.nestedbeans.exclusions;

import org.mapstruct.Mapper;

/**
* @author Filip Hrisafov
*/
@Mapper
public interface ErroneousJavaInternalMapper {

Target map(Source entity);
}
@@ -0,0 +1,71 @@
/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.nestedbeans.exclusions;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;

/**
* @author Filip Hrisafov
*/
@WithClasses({
Source.class,
Target.class,
ErroneousJavaInternalMapper.class
})
@RunWith(AnnotationProcessorTestRunner.class)
@IssueKey("1154")
public class ErroneousJavaInternalTest {

@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousJavaInternalMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 29,
messageRegExp = "Can't map property \".*MyType date\" to \"java\\.util\\.Date date\"\\. Consider to " +
"declare/implement a mapping method: \"java\\.util\\.Date map\\(.*MyType value\\)\"\\."),
@Diagnostic(type = ErroneousJavaInternalMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 29,
messageRegExp = "Can't map property \".*MyType calendar\" to \"java\\.util\\.GregorianCalendar " +
"calendar\"\\. Consider to declare/implement a mapping method: \"java\\.util\\.GregorianCalendar " +
"map\\(.*MyType value\\)\"\\."),
@Diagnostic(type = ErroneousJavaInternalMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 29,
messageRegExp = "Can't map property \".*MyType localDate\" to \"java\\.time\\.LocalDate localDate\"\\" +
". Consider to declare/implement a mapping method: \"java\\.time\\.LocalDate map\\(.*MyType " +
"value\\)\"\\."),
@Diagnostic(type = ErroneousJavaInternalMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 29,
messageRegExp = "Can't map property \".*List<.*MyType> types\" to \".*List<.*String> types\"\\" +
". Consider to declare/implement a mapping method: \".*List<.*String> map\\(.*List<.*MyType> " +
"value\\)\"\\.")
})
@Test
public void shouldNotNestIntoJavaPackageObjects() throws Exception {
}
}

0 comments on commit 7fafa6f

Please sign in to comment.