#135 applied list of MethodSelectors idea.

This commit is contained in:
sjaakd 2014-02-24 21:58:31 +01:00 committed by Gunnar Morling
parent 4d22ff7abd
commit b84a5df84e
6 changed files with 390 additions and 77 deletions

View File

@ -0,0 +1,80 @@
/**
* Copyright 2012-2014 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.model.source.selector;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
/**
* Selects on inheritance distance, e.g. the amount of inheritance steps from the parameter type.
*
* @author Sjaak Derksen
*/
public class InheritanceSelector implements MethodSelector {
/**
* {@inheritDoc} {@link MethodSelector}
*/
@Override
public <T extends Method> List<T> getMatchingMethods(
SourceMethod mappingMethod,
Iterable<T> methods,
Type parameterType,
Type returnType,
String targetPropertyName
) {
List<T> candidatesWithBestMatchingSourceType = new ArrayList<T>();
int bestMatchingSourceTypeDistance = Integer.MAX_VALUE;
// find the methods with the minimum distance regarding getParameter getParameter type
for (T method : methods ) {
Parameter singleSourceParam = method.getSourceParameters().iterator().next();
int sourceTypeDistance = parameterType.distanceTo( singleSourceParam.getType() );
bestMatchingSourceTypeDistance =
addToCandidateListIfMinimal(
candidatesWithBestMatchingSourceType,
bestMatchingSourceTypeDistance,
method,
sourceTypeDistance
);
}
return candidatesWithBestMatchingSourceType;
}
private <T extends Method> int addToCandidateListIfMinimal(List<T> candidatesWithBestMathingType,
int bestMatchingTypeDistance, T method,
int currentTypeDistance) {
if ( currentTypeDistance == bestMatchingTypeDistance ) {
candidatesWithBestMathingType.add( method );
}
else if ( currentTypeDistance < bestMatchingTypeDistance ) {
bestMatchingTypeDistance = currentTypeDistance;
candidatesWithBestMathingType.clear();
candidatesWithBestMathingType.add( method );
}
return bestMatchingTypeDistance;
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright 2012-2014 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.model.source.selector;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
/**
* This class provides the initial set of methods {@link MethodMatcher}
* @author Sjaak Derksen
*/
public class InitialSelector implements MethodSelector {
/**
* {@inheritDoc} {@link MethodSelector}
*/
@Override
public <T extends Method> List<T> getMatchingMethods(
SourceMethod mappingMethod,
Iterable<T> methods,
Type parameterType,
Type returnType,
String targetPropertyName
) {
List<T> result = new ArrayList<T>();
for ( T method : methods ) {
if ( method.getSourceParameters().size() != 1 ) {
continue;
}
if ( method.matches( parameterType, returnType ) ) {
result.add( method );
}
}
return result;
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright 2012-2014 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.model.source.selector;
import java.util.List;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
/**
*
* @author Sjaak Derksen
*/
public interface MethodSelector {
/**
* Selects a method
*
* @param <T> either SourceMethod or BuiltInMethod
* @param mappingMethod mapping method, defined in Mapper for which this selection is carried out
* @param methods set from available methods
* @param parameterType parameter type that should be matched
* @param returnType return type that should be matched
* @param targetPropertyName some information can be derived from the target property
* @return list of methods that passes the matching process
*/
<T extends Method> List<T> getMatchingMethods(
SourceMethod mappingMethod,
Iterable<T> methods,
Type parameterType,
Type returnType,
String targetPropertyName
);
}

View File

@ -0,0 +1,69 @@
/**
* Copyright 2012-2014 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.model.source.selector;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.util.Types;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
/**
*
* @author Sjaak Derksen
*/
public class MethodSelectors implements MethodSelector {
private final List<MethodSelector> selectors = new ArrayList<MethodSelector>();
public MethodSelectors( Types typeUtils ) {
selectors.add( new InitialSelector() );
selectors.add( new InheritanceSelector() );
selectors.add( new XmlElementDeclSelector( typeUtils ) );
}
@Override
public <T extends Method> List<T> getMatchingMethods(
SourceMethod mappingMethod,
Iterable<T> methods,
Type parameterType,
Type returnType,
String targetPropertyName
) {
List<T> candidates = new ArrayList<T>();
for (T method : methods) {
candidates.add( method );
}
for ( MethodSelector selector : selectors ) {
candidates = selector.getMatchingMethods(
mappingMethod,
candidates,
parameterType,
returnType,
targetPropertyName
);
}
return candidates;
}
}

View File

@ -0,0 +1,120 @@
/**
* Copyright 2012-2014 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.model.source.selector;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.prism.XmlElementDeclPrism;
/**
* This class matches XmlElmentDecl annotation. Matching happens in the following order.
* 1) Name and Scope matches
* 2) Scope matches
* 3) Name matches
* 4) No match at all.
*
* If there are Name and Scope matches, only those will be returned, otherwise the next in line (scope matches), etc.
*
* @author Sjaak Derksen
*/
public class XmlElementDeclSelector implements MethodSelector {
private final Types typeUtils;
public XmlElementDeclSelector( Types typeUtils ) {
this.typeUtils = typeUtils;
}
/**
* {@inheritDoc} {@link MethodSelector}
*/
@Override
public <T extends Method> List<T> getMatchingMethods(
SourceMethod mappingMethod,
Iterable<T> methods,
Type parameterType,
Type returnType,
String targetPropertyName
) {
List<T> noXmlDeclMatch = new ArrayList<T>();
List<T> nameMatch = new ArrayList<T>();
List<T> scopeMatch = new ArrayList<T>();
List<T> nameAndScopeMatch = new ArrayList<T>();
for ( T candidate : methods ) {
if ( candidate instanceof SourceMethod ) {
SourceMethod candiateMethod = (SourceMethod) candidate;
XmlElementDeclPrism xmlElememtDecl
= XmlElementDeclPrism.getInstanceOn( candiateMethod.getExecutable() );
if ( xmlElememtDecl != null ) {
String name = xmlElememtDecl.name();
TypeMirror scope = xmlElememtDecl.scope();
TypeMirror target = mappingMethod.getExecutable().getReturnType();
if ( ( scope != null ) && ( name != null ) ) {
// both scope and name should match when both defined
if ( name.equals( targetPropertyName ) && typeUtils.isSameType( scope, target ) ) {
nameAndScopeMatch.add( candidate );
}
}
else if ( ( scope == null ) && ( name != null ) ) {
// name should match when defined
if ( name.equals( targetPropertyName ) ) {
nameMatch.add( candidate );
}
}
else if ( ( scope != null ) && ( name == null ) ) {
// scope should match when defined
if ( typeUtils.isSameType( scope, target ) ) {
scopeMatch.add( candidate );
}
}
else {
// cannot make verdict based on scope or name, so add
noXmlDeclMatch.add( candidate );
}
}
else {
// cannot a verdict on xmldeclannotation, so add
noXmlDeclMatch.add( candidate );
}
}
else {
// cannot a verdict on xmldeclannotation, so add
noXmlDeclMatch.add( candidate );
}
}
if ( nameAndScopeMatch.size() > 0 ) {
return nameAndScopeMatch;
}
else if ( scopeMatch.size() > 0 ) {
return scopeMatch;
}
else if ( nameMatch.size() > 0 ) {
return nameMatch;
}
return noXmlDeclMatch;
}
}

View File

@ -62,10 +62,10 @@ import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.builtin.BuiltInMappingMethods;
import org.mapstruct.ap.model.source.builtin.BuiltInMethod;
import org.mapstruct.ap.model.source.selector.MethodSelectors;
import org.mapstruct.ap.option.Options;
import org.mapstruct.ap.option.ReportingPolicy;
import org.mapstruct.ap.prism.MapperPrism;
import org.mapstruct.ap.prism.XmlElementDeclPrism;
import org.mapstruct.ap.util.Executables;
import org.mapstruct.ap.util.Filters;
import org.mapstruct.ap.util.Strings;
@ -87,6 +87,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
private Conversions conversions;
private BuiltInMappingMethods builtInMethods;
private MethodSelectors methodSelectors;
/**
* Private methods which are not present in the original mapper interface and are added to map certain property
* types.
@ -105,6 +107,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
this.builtInMethods = new BuiltInMappingMethods( typeFactory );
this.virtualMethods = new HashSet<VirtualMappingMethod>();
this.methodSelectors = new MethodSelectors( typeUtils );
return getMapper( mapperTypeElement, sourceModel );
}
@ -954,47 +957,16 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
Type parameterType,
Type returnType,
String targetPropertyName) {
List<T> candidatesWithMathingTargetType = new ArrayList<T>();
for ( T method : methods ) {
if ( method.getSourceParameters().size() != 1 ) {
continue;
}
if ( method.matches( parameterType, returnType ) ) {
candidatesWithMathingTargetType.add( method );
}
}
List<T> candidatesWithBestMatchingSourceType = new ArrayList<T>();
int bestMatchingSourceTypeDistance = Integer.MAX_VALUE;
// find the methods with the minimum distance regarding getParameter getParameter type
for ( T method : candidatesWithMathingTargetType ) {
Parameter singleSourceParam = method.getSourceParameters().iterator().next();
int sourceTypeDistance = parameterType.distanceTo( singleSourceParam.getType() );
bestMatchingSourceTypeDistance =
addToCandidateListIfMinimal(
candidatesWithBestMatchingSourceType,
bestMatchingSourceTypeDistance,
method,
sourceTypeDistance
);
}
List<T> candidates = methodSelectors.getMatchingMethods(
mappingMethod,
methods,
parameterType,
returnType,
targetPropertyName );
// print a warning if we find more than one method with minimum getParameter type distance
if ( candidatesWithBestMatchingSourceType.size() > 1 ) {
// OK, we have more candidats. Lets see if we can use additional criteria to determine which is better
T result = getBestMatchBasedOnXmlElementDecl(
candidatesWithMathingTargetType,
mappingMethod,
targetPropertyName
);
if (result != null) {
return result;
}
if ( candidates.size() > 1 ) {
messager.printMessage(
Kind.ERROR,
@ -1002,14 +974,14 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
"Ambiguous mapping methods found for mapping " + mappedElement + " from %s to %s: %s.",
parameterType,
returnType,
Strings.join( candidatesWithBestMatchingSourceType, ", " )
Strings.join( candidates, ", " )
),
mappingMethod.getExecutable()
);
}
if ( !candidatesWithBestMatchingSourceType.isEmpty() ) {
return candidatesWithBestMatchingSourceType.get( 0 );
if ( !candidates.isEmpty() ) {
return candidates.get( 0 );
}
return null;
@ -1208,39 +1180,4 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
return alternativeTargetAccessorsMethods;
}
/**
* This method uses the {@link javax.xml.bind.annotation.XmlElementDecl } annotation to determine if the scope
* of the mapping method matches the mapper method
* @param <T> can be SourceMethod or BuildInType. Only SourceMethods qualify
* @param candidates list with potential matches
* @param mapperMethod mapper method for which this is analyzed
* @param targetPropertyName the property name of the target
* @return a method (always SourceMethod) when there's a scope that matches
*/
private <T extends Method> T getBestMatchBasedOnXmlElementDecl(
List<T> candidates,
SourceMethod mapperMethod,
String targetPropertyName) {
for (T candidate : candidates ) {
if (candidate instanceof SourceMethod) {
SourceMethod candiateMethod = (SourceMethod) candidate;
XmlElementDeclPrism xmlElememtDecl =
XmlElementDeclPrism.getInstanceOn( candiateMethod.getExecutable() );
if ( xmlElememtDecl != null ) {
String name = xmlElememtDecl.name();
TypeMirror scope = xmlElememtDecl.scope();
TypeMirror target = mapperMethod.getExecutable().getReturnType();
if ( scope != null && typeUtils.isSameType( scope, target ) ) {
if ( ( name != null ) && ( name.equals( targetPropertyName ) ) ) {
return candidate;
}
}
}
}
}
return null;
}
}