001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.beans.support;
018
019import java.beans.PropertyEditor;
020import java.lang.reflect.Method;
021
022import org.springframework.beans.PropertyEditorRegistry;
023import org.springframework.beans.SimpleTypeConverter;
024import org.springframework.beans.TypeConverter;
025import org.springframework.beans.TypeMismatchException;
026import org.springframework.lang.Nullable;
027import org.springframework.util.Assert;
028import org.springframework.util.MethodInvoker;
029import org.springframework.util.ReflectionUtils;
030
031/**
032 * Subclass of {@link MethodInvoker} that tries to convert the given
033 * arguments for the actual target method via a {@link TypeConverter}.
034 *
035 * <p>Supports flexible argument conversions, in particular for
036 * invoking a specific overloaded method.
037 *
038 * @author Juergen Hoeller
039 * @since 1.1
040 * @see org.springframework.beans.BeanWrapperImpl#convertIfNecessary
041 */
042public class ArgumentConvertingMethodInvoker extends MethodInvoker {
043
044        @Nullable
045        private TypeConverter typeConverter;
046
047        private boolean useDefaultConverter = true;
048
049
050        /**
051         * Set a TypeConverter to use for argument type conversion.
052         * <p>Default is a {@link org.springframework.beans.SimpleTypeConverter}.
053         * Can be overridden with any TypeConverter implementation, typically
054         * a pre-configured SimpleTypeConverter or a BeanWrapperImpl instance.
055         * @see org.springframework.beans.SimpleTypeConverter
056         * @see org.springframework.beans.BeanWrapperImpl
057         */
058        public void setTypeConverter(@Nullable TypeConverter typeConverter) {
059                this.typeConverter = typeConverter;
060                this.useDefaultConverter = (typeConverter == null);
061        }
062
063        /**
064         * Return the TypeConverter used for argument type conversion.
065         * <p>Can be cast to {@link org.springframework.beans.PropertyEditorRegistry}
066         * if direct access to the underlying PropertyEditors is desired
067         * (provided that the present TypeConverter actually implements the
068         * PropertyEditorRegistry interface).
069         */
070        @Nullable
071        public TypeConverter getTypeConverter() {
072                if (this.typeConverter == null && this.useDefaultConverter) {
073                        this.typeConverter = getDefaultTypeConverter();
074                }
075                return this.typeConverter;
076        }
077
078        /**
079         * Obtain the default TypeConverter for this method invoker.
080         * <p>Called if no explicit TypeConverter has been specified.
081         * The default implementation builds a
082         * {@link org.springframework.beans.SimpleTypeConverter}.
083         * Can be overridden in subclasses.
084         */
085        protected TypeConverter getDefaultTypeConverter() {
086                return new SimpleTypeConverter();
087        }
088
089        /**
090         * Register the given custom property editor for all properties of the given type.
091         * <p>Typically used in conjunction with the default
092         * {@link org.springframework.beans.SimpleTypeConverter}; will work with any
093         * TypeConverter that implements the PropertyEditorRegistry interface as well.
094         * @param requiredType type of the property
095         * @param propertyEditor editor to register
096         * @see #setTypeConverter
097         * @see org.springframework.beans.PropertyEditorRegistry#registerCustomEditor
098         */
099        public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
100                TypeConverter converter = getTypeConverter();
101                if (!(converter instanceof PropertyEditorRegistry)) {
102                        throw new IllegalStateException(
103                                        "TypeConverter does not implement PropertyEditorRegistry interface: " + converter);
104                }
105                ((PropertyEditorRegistry) converter).registerCustomEditor(requiredType, propertyEditor);
106        }
107
108
109        /**
110         * This implementation looks for a method with matching parameter types.
111         * @see #doFindMatchingMethod
112         */
113        @Override
114        protected Method findMatchingMethod() {
115                Method matchingMethod = super.findMatchingMethod();
116                // Second pass: look for method where arguments can be converted to parameter types.
117                if (matchingMethod == null) {
118                        // Interpret argument array as individual method arguments.
119                        matchingMethod = doFindMatchingMethod(getArguments());
120                }
121                if (matchingMethod == null) {
122                        // Interpret argument array as single method argument of array type.
123                        matchingMethod = doFindMatchingMethod(new Object[] {getArguments()});
124                }
125                return matchingMethod;
126        }
127
128        /**
129         * Actually find a method with matching parameter type, i.e. where each
130         * argument value is assignable to the corresponding parameter type.
131         * @param arguments the argument values to match against method parameters
132         * @return a matching method, or {@code null} if none
133         */
134        @Nullable
135        protected Method doFindMatchingMethod(Object[] arguments) {
136                TypeConverter converter = getTypeConverter();
137                if (converter != null) {
138                        String targetMethod = getTargetMethod();
139                        Method matchingMethod = null;
140                        int argCount = arguments.length;
141                        Class<?> targetClass = getTargetClass();
142                        Assert.state(targetClass != null, "No target class set");
143                        Method[] candidates = ReflectionUtils.getAllDeclaredMethods(targetClass);
144                        int minTypeDiffWeight = Integer.MAX_VALUE;
145                        Object[] argumentsToUse = null;
146                        for (Method candidate : candidates) {
147                                if (candidate.getName().equals(targetMethod)) {
148                                        // Check if the inspected method has the correct number of parameters.
149                                        int parameterCount = candidate.getParameterCount();
150                                        if (parameterCount == argCount) {
151                                                Class<?>[] paramTypes = candidate.getParameterTypes();
152                                                Object[] convertedArguments = new Object[argCount];
153                                                boolean match = true;
154                                                for (int j = 0; j < argCount && match; j++) {
155                                                        // Verify that the supplied argument is assignable to the method parameter.
156                                                        try {
157                                                                convertedArguments[j] = converter.convertIfNecessary(arguments[j], paramTypes[j]);
158                                                        }
159                                                        catch (TypeMismatchException ex) {
160                                                                // Ignore -> simply doesn't match.
161                                                                match = false;
162                                                        }
163                                                }
164                                                if (match) {
165                                                        int typeDiffWeight = getTypeDifferenceWeight(paramTypes, convertedArguments);
166                                                        if (typeDiffWeight < minTypeDiffWeight) {
167                                                                minTypeDiffWeight = typeDiffWeight;
168                                                                matchingMethod = candidate;
169                                                                argumentsToUse = convertedArguments;
170                                                        }
171                                                }
172                                        }
173                                }
174                        }
175                        if (matchingMethod != null) {
176                                setArguments(argumentsToUse);
177                                return matchingMethod;
178                        }
179                }
180                return null;
181        }
182
183}