Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
Expand Down Expand Up @@ -2071,7 +2072,12 @@ private void reportErrorForUnusedSourceParameters() {
Type parameterType = sourceParameter.getType();
if ( parameterType.isMapType() ) {
// We are only going to output a warning for the source parameter if it was unused
// i.e. the intention of the user was most likely to use it as a mapping from Bean to Map
// i.e. the intention of the user was most likely to use it as a mapping from Bean to Map.
// A parameter that is referenced from a Java expression is used, even though it does not
// take part in the property mapping itself.
if ( isReferencedFromJavaExpression( sourceParameter ) ) {
continue;
}
List<Type> typeParameters = parameterType.getTypeParameters();
if ( typeParameters.size() != 2 || !typeParameters.get( 0 ).isString() ) {
Message message = typeParameters.isEmpty() ?
Expand All @@ -2092,6 +2098,26 @@ private void reportErrorForUnusedSourceParameters() {
}
}
}

private boolean isReferencedFromJavaExpression(Parameter sourceParameter) {
Pattern reference = Pattern.compile( "\\b" + Pattern.quote( sourceParameter.getName() ) + "\\b" );
for ( MappingOptions mapping : method.getOptions().getMappings() ) {
if ( isReferencedBy( reference, mapping.getJavaExpression() )
|| isReferencedBy( reference, mapping.getDefaultJavaExpression() )
|| isReferencedBy( reference, mapping.getConditionJavaExpression() ) ) {
return true;
}
}

return false;
}

private boolean isReferencedBy(Pattern reference, String javaExpression) {
// The parameter name is looked up in the raw expression text, so a name that occurs in a string
// literal or as a member name counts as a reference as well. That can only suppress the warning,
// never raise a new one, which is the safe direction for a heuristic hint like this one.
return javaExpression != null && reference.matcher( javaExpression ).find();
}
}

private static class ConstructorAccessor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,69 @@ void shouldNotWarnIfMappedIsUsedAsSourceParameter() {
.containsOnly( entry( "10", "value" ) );
}

@ProcessorTest
@IssueKey("3861")
@WithClasses({
MapToBeanNonStringMapUsedInExpressionMapper.class
})
void shouldNotWarnIfMapParameterIsUsedInExpression() {
Map<Integer, Integer> values = new HashMap<>();
values.put( 1, 10 );
values.put( 2, 20 );

MapToBeanNonStringMapUsedInExpressionMapper.Target target =
MapToBeanNonStringMapUsedInExpressionMapper.INSTANCE.toTarget( "test", values );

assertThat( target.getValue() ).isEqualTo( "test" );
assertThat( target.getSum() ).isEqualTo( 30 );
}

@ProcessorTest
@IssueKey("3861")
@WithClasses({
MapToBeanNonStringMapUsedInDefaultExpressionMapper.class
})
void shouldNotWarnIfMapParameterIsUsedInDefaultExpression() {
Map<Integer, Integer> values = new HashMap<>();
values.put( 1, 10 );
values.put( 2, 20 );

MapToBeanNonStringMapUsedInDefaultExpressionMapper.Target withoutSum =
MapToBeanNonStringMapUsedInDefaultExpressionMapper.INSTANCE
.toTarget( new MapToBeanNonStringMapUsedInDefaultExpressionMapper.Source( null ), values );

assertThat( withoutSum.getSum() ).isEqualTo( 30 );

MapToBeanNonStringMapUsedInDefaultExpressionMapper.Target withSum =
MapToBeanNonStringMapUsedInDefaultExpressionMapper.INSTANCE
.toTarget( new MapToBeanNonStringMapUsedInDefaultExpressionMapper.Source( 5 ), values );

assertThat( withSum.getSum() ).isEqualTo( 5 );
}

@ProcessorTest
@IssueKey("3861")
@WithClasses({
MapToBeanNonStringMapUsedInConditionExpressionMapper.class
})
void shouldNotWarnIfMapParameterIsUsedInConditionExpression() {
MapToBeanNonStringMapUsedInConditionExpressionMapper.Source source =
new MapToBeanNonStringMapUsedInConditionExpressionMapper.Source( 5 );

MapToBeanNonStringMapUsedInConditionExpressionMapper.Target notMapped =
MapToBeanNonStringMapUsedInConditionExpressionMapper.INSTANCE.toTarget( source, new HashMap<>() );

assertThat( notMapped.getSum() ).isNull();

Map<Integer, Integer> values = new HashMap<>();
values.put( 1, 10 );

MapToBeanNonStringMapUsedInConditionExpressionMapper.Target mapped =
MapToBeanNonStringMapUsedInConditionExpressionMapper.INSTANCE.toTarget( source, values );

assertThat( mapped.getSum() ).isEqualTo( 5 );
}

@ProcessorTest
@WithClasses(MapToBeanImplicitUnmappedSourcePolicyMapper.class)
void shouldNotReportUnmappedSourcePropertiesWithMap() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.frommap;

import java.util.Map;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface MapToBeanNonStringMapUsedInConditionExpressionMapper {

MapToBeanNonStringMapUsedInConditionExpressionMapper INSTANCE =
Mappers.getMapper( MapToBeanNonStringMapUsedInConditionExpressionMapper.class );

@Mapping(target = "sum", source = "source.sum", conditionExpression = "java(!values.isEmpty())")
Target toTarget(Source source, Map<Integer, Integer> values);

class Source {

private final Integer sum;

public Source(Integer sum) {
this.sum = sum;
}

public Integer getSum() {
return sum;
}
}

class Target {

private Integer sum;

public Integer getSum() {
return sum;
}

public void setSum(Integer sum) {
this.sum = sum;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.frommap;

import java.util.Map;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface MapToBeanNonStringMapUsedInDefaultExpressionMapper {

MapToBeanNonStringMapUsedInDefaultExpressionMapper INSTANCE =
Mappers.getMapper( MapToBeanNonStringMapUsedInDefaultExpressionMapper.class );

@Mapping(target = "sum", source = "source.sum", defaultExpression = "java(calculateSum( values ))")
Target toTarget(Source source, Map<Integer, Integer> values);

default int calculateSum(Map<Integer, Integer> values) {
int sum = 0;
for ( Integer value : values.values() ) {
sum += value;
}
return sum;
}

class Source {

private final Integer sum;

public Source(Integer sum) {
this.sum = sum;
}

public Integer getSum() {
return sum;
}
}

class Target {

private Integer sum;

public Integer getSum() {
return sum;
}

public void setSum(Integer sum) {
this.sum = sum;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.frommap;

import java.util.Map;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface MapToBeanNonStringMapUsedInExpressionMapper {

MapToBeanNonStringMapUsedInExpressionMapper INSTANCE =
Mappers.getMapper( MapToBeanNonStringMapUsedInExpressionMapper.class );

@Mapping(target = "sum", expression = "java(calculateSum( values ))")
Target toTarget(String value, Map<Integer, Integer> values);

default int calculateSum(Map<Integer, Integer> values) {
int sum = 0;
for ( Integer value : values.values() ) {
sum += value;
}
return sum;
}

class Target {

private final String value;
private final int sum;

public Target(String value, int sum) {
this.value = value;
this.sum = sum;
}

public String getValue() {
return value;
}

public int getSum() {
return sum;
}
}

}