001/*
002 * Copyright 2002-2012 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.util.MethodInvoker;
027import org.springframework.util.ReflectionUtils;
028
029/**
030 * Subclass of {@link MethodInvoker} that tries to convert the given
031 * arguments for the actual target method via a {@link TypeConverter}.
032 *
033 * <p>Supports flexible argument conversions, in particular for
034 * invoking a specific overloaded method.
035 *
036 * @author Juergen Hoeller
037 * @since 1.1
038 * @see org.springframework.beans.BeanWrapperImpl#convertIfNecessary
039 */
040public class ArgumentConvertingMethodInvoker extends MethodInvoker {
041
042        private TypeConverter typeConverter;
043
044        private boolean useDefaultConverter = true;
045
046
047        /**
048         * Set a TypeConverter to use for argument type conversion.
049         * <p>Default is a {@link org.springframework.beans.SimpleTypeConverter}.
050         * Can be overridden with any TypeConverter implementation, typically
051         * a pre-configured SimpleTypeConverter or a BeanWrapperImpl instance.
052         * @see org.springframework.beans.SimpleTypeConverter
053         * @see org.springframework.beans.BeanWrapperImpl
054         */
055        public void setTypeConverter(TypeConverter typeConverter) {
056                this.typeConverter = typeConverter;
057                this.useDefaultConverter = false;
058        }
059
060        /**
061         * Return the TypeConverter used for argument type conversion.
062         * <p>Can be cast to {@link org.springframework.beans.PropertyEditorRegistry}
063         * if direct access to the underlying PropertyEditors is desired
064         * (provided that the present TypeConverter actually implements the
065         * PropertyEditorRegistry interface).
066         */
067        public TypeConverter getTypeConverter() {
068                if (this.typeConverter == null && this.useDefaultConverter) {
069                        this.typeConverter = getDefaultTypeConverter();
070                }
071                return this.typeConverter;
072        }
073
074        /**
075         * Obtain the default TypeConverter for this method invoker.
076         * <p>Called if no explicit TypeConverter has been specified.
077         * The default implementation builds a
078         * {@link org.springframework.beans.SimpleTypeConverter}.
079         * Can be overridden in subclasses.
080         */
081        protected TypeConverter getDefaultTypeConverter() {
082                return new SimpleTypeConverter();
083        }
084
085        /**
086         * Register the given custom property editor for all properties of the given type.
087         * <p>Typically used in conjunction with the default
088         * {@link org.springframework.beans.SimpleTypeConverter}; will work with any
089         * TypeConverter that implements the PropertyEditorRegistry interface as well.
090         * @param requiredType type of the property
091         * @param propertyEditor editor to register
092         * @see #setTypeConverter
093         * @see org.springframework.beans.PropertyEditorRegistry#registerCustomEditor
094         */
095        public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
096                TypeConverter converter = getTypeConverter();
097                if (!(converter instanceof PropertyEditorRegistry)) {
098                        throw new IllegalStateException(
099                                        "TypeConverter does not implement PropertyEditorRegistry interface: " + converter);
100                }
101                ((PropertyEditorRegistry) converter).registerCustomEditor(requiredType, propertyEditor);
102        }
103
104
105        /**
106         * This implementation looks for a method with matching parameter types.
107         * @see #doFindMatchingMethod
108         */
109        @Override
110        protected Method findMatchingMethod() {
111                Method matchingMethod = super.findMatchingMethod();
112                // Second pass: look for method where arguments can be converted to parameter types.
113                if (matchingMethod == null) {
114                        // Interpret argument array as individual method arguments.
115                        matchingMethod = doFindMatchingMethod(getArguments());
116                }
117                if (matchingMethod == null) {
118                        // Interpret argument array as single method argument of array type.
119                        matchingMethod = doFindMatchingMethod(new Object[] {getArguments()});
120                }
121                return matchingMethod;
122        }
123
124        /**
125         * Actually find a method with matching parameter type, i.e. where each
126         * argument value is assignable to the corresponding parameter type.
127         * @param arguments the argument values to match against method parameters
128         * @return a matching method, or {@code null} if none
129         */
130        protected Method doFindMatchingMethod(Object[] arguments) {
131                TypeConverter converter = getTypeConverter();
132                if (converter != null) {
133                        String targetMethod = getTargetMethod();
134                        Method matchingMethod = null;
135                        int argCount = arguments.length;
136                        Method[] candidates = ReflectionUtils.getAllDeclaredMethods(getTargetClass());
137                        int minTypeDiffWeight = Integer.MAX_VALUE;
138                        Object[] argumentsToUse = null;
139                        for (Method candidate : candidates) {
140                                if (candidate.getName().equals(targetMethod)) {
141                                        // Check if the inspected method has the correct number of parameters.
142                                        Class<?>[] paramTypes = candidate.getParameterTypes();
143                                        if (paramTypes.length == argCount) {
144                                                Object[] convertedArguments = new Object[argCount];
145                                                boolean match = true;
146                                                for (int j = 0; j < argCount && match; j++) {
147                                                        // Verify that the supplied argument is assignable to the method parameter.
148                                                        try {
149                                                                convertedArguments[j] = converter.convertIfNecessary(arguments[j], paramTypes[j]);
150                                                        }
151                                                        catch (TypeMismatchException ex) {
152                                                                // Ignore -> simply doesn't match.
153                                                                match = false;
154                                                        }
155                                                }
156                                                if (match) {
157                                                        int typeDiffWeight = getTypeDifferenceWeight(paramTypes, convertedArguments);
158                                                        if (typeDiffWeight < minTypeDiffWeight) {
159                                                                minTypeDiffWeight = typeDiffWeight;
160                                                                matchingMethod = candidate;
161                                                                argumentsToUse = convertedArguments;
162                                                        }
163                                                }
164                                        }
165                                }
166                        }
167                        if (matchingMethod != null) {
168                                setArguments(argumentsToUse);
169                                return matchingMethod;
170                        }
171                }
172                return null;
173        }
174
175}