#61 Raising an error in case a collection/map attribute can't be propagated due to differently parameterized attributes in source and target

This commit is contained in:
Gunnar Morling 2013-08-15 23:45:46 +02:00
parent 186c127ebd
commit be3018b614
9 changed files with 323 additions and 61 deletions

View File

@ -30,9 +30,13 @@ import java.util.Set;
import javax.annotation.processing.Messager; import javax.annotation.processing.Messager;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.MapperPrism; import org.mapstruct.ap.MapperPrism;
@ -71,6 +75,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
private static final String IMPLEMENTATION_SUFFIX = "Impl"; private static final String IMPLEMENTATION_SUFFIX = "Impl";
private Elements elementUtils; private Elements elementUtils;
private Types typeUtils;
private Messager messager; private Messager messager;
private Options options; private Options options;
@ -82,6 +87,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
@Override @Override
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<Method> sourceModel) { public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<Method> sourceModel) {
this.elementUtils = context.getElementUtils(); this.elementUtils = context.getElementUtils();
this.typeUtils = context.getTypeUtils();
this.messager = context.getMessager(); this.messager = context.getMessager();
this.options = context.getOptions(); this.options = context.getOptions();
@ -554,18 +560,42 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
} }
/** /**
* Reports an error if source and target type of the property are different * Reports an error if source the property can't be mapped from source to target. A mapping if possible if one of
* and neither a mapping method nor a conversion exists nor the property is * the following conditions is true:
* of a collection type with default implementation * <ul>
* <li>the source type is assignable to the target type</li>
* <li>a mapping method exists</li>
* <li>a built-in conversion exists</li>
* <li>the property is of a collection or map type and the constructor of the target type (either itself or its
* implementation type) accepts the source type.</li>
* </ul>
* *
* @param method The mapping method owning the property mapping. * @param method The mapping method owning the property mapping.
* @param property The property mapping to check. * @param property The property mapping to check.
*/ */
private void reportErrorIfPropertyCanNotBeMapped(Method method, PropertyMapping property) { private void reportErrorIfPropertyCanNotBeMapped(Method method, PropertyMapping property) {
boolean collectionOrMapTargetTypeHasCompatibleConstructor = false;
if ( property.getTargetType().isCollectionType() || property.getTargetType().isMapType() ) {
if ( property.getTargetType().getImplementationType() != null ) {
collectionOrMapTargetTypeHasCompatibleConstructor = hasCompatibleConstructor(
property.getSourceType(),
property.getTargetType().getImplementationType()
);
}
else {
collectionOrMapTargetTypeHasCompatibleConstructor = hasCompatibleConstructor(
property.getSourceType(),
property.getTargetType()
);
}
}
if ( property.getSourceType().isAssignableTo( property.getTargetType() ) || if ( property.getSourceType().isAssignableTo( property.getTargetType() ) ||
property.getMappingMethod() != null || property.getMappingMethod() != null ||
property.getConversion() != null || property.getConversion() != null ||
property.getTargetType().getImplementationType() != null ) { ( ( property.getTargetType().isCollectionType() || property.getTargetType().isMapType() ) &&
collectionOrMapTargetTypeHasCompatibleConstructor ) ) {
return; return;
} }
@ -581,4 +611,39 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
method.getExecutable() method.getExecutable()
); );
} }
/**
* Whether the given target type has a single-argument constructor which accepts the given source type.
*
* @param sourceType the source type
* @param targetType the target type
* @return {@code true} if the target type has a constructor accepting the given source type, {@code false}
* otherwise.
*/
private boolean hasCompatibleConstructor(Type sourceType, Type targetType) {
List<ExecutableElement> targetTypeConstructors = ElementFilter.constructorsIn(
targetType.getTypeElement()
.getEnclosedElements()
);
for ( ExecutableElement constructor : targetTypeConstructors ) {
if ( constructor.getParameters().size() != 1 ) {
continue;
}
//get the constructor resolved against the type arguments of specific target type
ExecutableType typedConstructor = (ExecutableType) typeUtils.asMemberOf(
(DeclaredType) targetType.getTypeMirror(), constructor
);
if ( typeUtils.isAssignable(
sourceType.getTypeMirror(),
typedConstructor.getParameterTypes().iterator().next()
) ) {
return true;
}
}
return false;
}
} }

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2013 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.collection.erroneous;
import java.util.Map;
public class AnotherTarget {
private Map<String, String> barMap;
public Map<String, String> getBarMap() {
return barMap;
}
public void setBarMap(Map<String, String> barMap) {
this.barMap = barMap;
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright 2012-2013 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.collection.erroneous;
import org.mapstruct.Mapper;
@Mapper
public interface ErroneousCollectionMapper {
Target sourceToTarget(Source source);
}

View File

@ -0,0 +1,86 @@
/**
* Copyright 2012-2013 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.collection.erroneous;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.MapperTestBase;
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.testng.annotations.Test;
/**
* Test for illegal mappings between collection types, iterable and non-iterable types etc.
*
* @author Gunnar Morling
*/
public class ErroneousCollectionMappingTest extends MapperTestBase {
@Test
@IssueKey("6")
@WithClasses({ ErroneousCollectionToNonCollectionMapper.class })
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousCollectionToNonCollectionMapper.class,
kind = Kind.ERROR,
line = 28,
messageRegExp = "Can't generate mapping method from iterable type to non-iterable type"),
@Diagnostic(type = ErroneousCollectionToNonCollectionMapper.class,
kind = Kind.ERROR,
line = 30,
messageRegExp = "Can't generate mapping method from non-iterable type to iterable type")
}
)
public void shouldFailToGenerateImplementationBetweenCollectionAndNonCollection() {
}
@Test
@WithClasses({ ErroneousCollectionMapper.class, Source.class, Target.class })
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousCollectionMapper.class,
kind = Kind.ERROR,
line = 26,
messageRegExp = "Can't map property \"java\\.util\\.Set<java\\.lang\\.String> fooSet\" to" +
" \"java\\.util\\.Set<java\\.lang\\.Long> fooSet\""),
}
)
public void shouldFailToGenerateImplementationDueToDifferentlyParameterizedCollections() {
}
@Test
@WithClasses({ ErroneousMapMapper.class, Source.class, AnotherTarget.class })
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousMapMapper.class,
kind = Kind.ERROR,
line = 26,
messageRegExp = "Can't map property \"java\\.util\\.Map<java\\.lang\\.String,java\\.lang\\.Long>" +
" barMap\" to \"java.util.Map<java\\.lang\\.String,java\\.lang\\.String> barMap\"")
}
)
public void shouldFailToGenerateImplementationDueToDifferentlyParameterizedMaps() {
}
}

View File

@ -23,7 +23,7 @@ import java.util.Set;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
@Mapper @Mapper
public interface ErronuousMapper { public interface ErroneousCollectionToNonCollectionMapper {
Integer stringSetToInteger(Set<String> strings); Integer stringSetToInteger(Set<String> strings);

View File

@ -0,0 +1,27 @@
/**
* Copyright 2012-2013 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.collection.erroneous;
import org.mapstruct.Mapper;
@Mapper
public interface ErroneousMapMapper {
AnotherTarget sourceToTarget(Source source);
}

View File

@ -1,56 +0,0 @@
/**
* Copyright 2012-2013 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.collection.erroneous;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.MapperTestBase;
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.testng.annotations.Test;
/**
* Test for illegal mappings between iterable and non-iterable types.
*
* @author Gunnar Morling
*/
@WithClasses({ ErronuousMapper.class })
public class ErronuousCollectionMappingTest extends MapperTestBase {
@Test
@IssueKey("6")
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErronuousMapper.class,
kind = Kind.ERROR,
line = 28,
messageRegExp = "Can't generate mapping method from iterable type to non-iterable type\\."),
@Diagnostic(type = ErronuousMapper.class,
kind = Kind.ERROR,
line = 30,
messageRegExp = "Can't generate mapping method from non-iterable type to iterable type\\.")
}
)
public void shouldFailToGenerateMappingFromListToString() {
}
}

View File

@ -0,0 +1,45 @@
/**
* Copyright 2012-2013 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.collection.erroneous;
import java.util.Map;
import java.util.Set;
public class Source {
private Set<String> fooSet;
private Map<String, Long> barMap;
public Set<String> getFooSet() {
return fooSet;
}
public void setFooSet(Set<String> fooSet) {
this.fooSet = fooSet;
}
public Map<String, Long> getBarMap() {
return barMap;
}
public void setBarMap(Map<String, Long> barMap) {
this.barMap = barMap;
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2013 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.collection.erroneous;
import java.util.Set;
public class Target {
private Set<Long> fooSet;
public Set<Long> getFooSet() {
return fooSet;
}
public void setFooSet(Set<Long> fooSet) {
this.fooSet = fooSet;
}
}