/** * 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.internal.model.source.selector; import static org.mapstruct.ap.internal.util.Collections.first; import java.util.ArrayList; import java.util.List; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.MethodMatcher; /** * Selects those methods from the given input set which match the given source and target types (via * {@link MethodMatcher}). * * @author Sjaak Derksen */ public class TypeSelector implements MethodSelector { private TypeFactory typeFactory; public TypeSelector(TypeFactory typeFactory) { this.typeFactory = typeFactory; } @Override public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod, List<SelectedMethod<T>> methods, List<Type> sourceTypes, Type targetType, SelectionCriteria criteria) { if ( methods.isEmpty() ) { return methods; } List<SelectedMethod<T>> result = new ArrayList<SelectedMethod<T>>(); List<ParameterBinding> availableBindings; if ( sourceTypes.isEmpty() ) { // if no source types are given, we have a factory or lifecycle method availableBindings = getAvailableParameterBindingsFromMethod( mappingMethod, targetType ); } else { availableBindings = getAvailableParameterBindingsFromSourceTypes( sourceTypes, targetType, mappingMethod ); } for ( SelectedMethod<T> method : methods ) { List<List<ParameterBinding>> parameterBindingPermutations = getCandidateParameterBindingPermutations( availableBindings, method.getMethod().getParameters() ); if ( parameterBindingPermutations != null ) { SelectedMethod<T> matchingMethod = getFirstMatchingParameterBinding( targetType, method, parameterBindingPermutations ); if ( matchingMethod != null ) { result.add( matchingMethod ); } } } return result; } private List<ParameterBinding> getAvailableParameterBindingsFromMethod(Method method, Type targetType) { List<ParameterBinding> availableParams = new ArrayList<ParameterBinding>( method.getParameters().size() + 2 ); availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); addMappingTargetAndTargetTypeBindings( availableParams, targetType ); return availableParams; } private List<ParameterBinding> getAvailableParameterBindingsFromSourceTypes(List<Type> sourceTypes, Type targetType, Method mappingMethod) { List<ParameterBinding> availableParams = new ArrayList<ParameterBinding>( sourceTypes.size() + 2 ); addMappingTargetAndTargetTypeBindings( availableParams, targetType ); for ( Type sourceType : sourceTypes ) { availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); } for ( Parameter param : mappingMethod.getParameters() ) { if ( param.isMappingContext() ) { availableParams.add( ParameterBinding.fromParameter( param ) ); } } return availableParams; } private void addMappingTargetAndTargetTypeBindings(List<ParameterBinding> availableParams, Type targetType) { availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) ); availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); } private <T extends Method> SelectedMethod<T> getFirstMatchingParameterBinding(Type targetType, SelectedMethod<T> method, List<List<ParameterBinding>> parameterAssignmentVariants) { for ( List<ParameterBinding> parameterAssignments : parameterAssignmentVariants ) { if ( method.getMethod().matches( extractTypes( parameterAssignments ), targetType ) ) { method.setParameterBindings( parameterAssignments ); return method; } } return null; } /** * @param availableParams parameter bindings available in the scope of the method call * @param methodParameters parameters of the method that is inspected * @return all parameter binding permutations for which proper type checks need to be conducted. */ private static List<List<ParameterBinding>> getCandidateParameterBindingPermutations( List<ParameterBinding> availableParams, List<Parameter> methodParameters) { if ( methodParameters.size() > availableParams.size() ) { return null; } List<List<ParameterBinding>> bindingPermutations = new ArrayList<List<ParameterBinding>>( 1 ); bindingPermutations.add( new ArrayList<ParameterBinding>( methodParameters.size() ) ); for ( Parameter methodParam : methodParameters ) { List<ParameterBinding> candidateBindings = findCandidateBindingsForParameter( availableParams, methodParam ); if ( candidateBindings.isEmpty() ) { return null; } if ( candidateBindings.size() == 1 ) { // short-cut to avoid list-copies for the usual case where only one binding fits for ( List<ParameterBinding> variant : bindingPermutations ) { // add binding to each existing variant variant.add( first( candidateBindings ) ); } } else { List<List<ParameterBinding>> newVariants = new ArrayList<List<ParameterBinding>>( bindingPermutations.size() * candidateBindings.size() ); for ( List<ParameterBinding> variant : bindingPermutations ) { // create a copy of each variant for each binding for ( ParameterBinding binding : candidateBindings ) { List<ParameterBinding> extendedVariant = new ArrayList<ParameterBinding>( methodParameters.size() ); extendedVariant.addAll( variant ); extendedVariant.add( binding ); newVariants.add( extendedVariant ); } } bindingPermutations = newVariants; } } return bindingPermutations; } /** * @param candidateParameters available for assignment. * @param parameter that need assignment from one of the candidate parameter bindings. * @return list of candidate parameter bindings that might be assignable. */ private static List<ParameterBinding> findCandidateBindingsForParameter(List<ParameterBinding> candidateParameters, Parameter parameter) { List<ParameterBinding> result = new ArrayList<ParameterBinding>( candidateParameters.size() ); for ( ParameterBinding candidate : candidateParameters ) { if ( parameter.isTargetType() == candidate.isTargetType() && parameter.isMappingTarget() == candidate.isMappingTarget() && parameter.isMappingContext() == candidate.isMappingContext() ) { result.add( candidate ); } } return result; } private static List<Type> extractTypes(List<ParameterBinding> parameters) { List<Type> result = new ArrayList<Type>( parameters.size() ); for ( ParameterBinding param : parameters ) { result.add( param.getType() ); } return result; } }