001: /*
002: * Copyright 2004-2007 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: * http://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: package org.springframework.binding.method;
017:
018: import java.io.Serializable;
019: import java.lang.reflect.Method;
020: import java.util.HashMap;
021: import java.util.Map;
022:
023: import org.springframework.util.Assert;
024: import org.springframework.util.ClassUtils;
025: import org.springframework.util.ObjectUtils;
026:
027: /**
028: * A helper for resolving and caching a Java method by reflection.
029: *
030: * @author Keith Donald
031: */
032: public class MethodKey implements Serializable {
033:
034: /**
035: * The class the method is a member of.
036: */
037: private Class declaredType;
038:
039: /**
040: * The method name.
041: */
042: private String methodName;
043:
044: /**
045: * The method's actual parameter types. Could contain null values
046: * if the user did not specify a parameter type for the corresponding
047: * parameter
048: */
049: private Class[] parameterTypes;
050:
051: /**
052: * A cached handle to the resolved method (may be null).
053: */
054: private transient Method method;
055:
056: /**
057: * Create a new method key.
058: * @param declaredType the class the method is a member of
059: * @param methodName the method name
060: * @param parameterTypes the method's parameter types, or <code>null</code>
061: * if the method has no parameters
062: */
063: public MethodKey(Class declaredType, String methodName,
064: Class[] parameterTypes) {
065: Assert.notNull(declaredType,
066: "The method's declared type is required");
067: Assert.notNull(methodName, "The method name is required");
068: this .declaredType = declaredType;
069: this .methodName = methodName;
070: this .parameterTypes = parameterTypes;
071: }
072:
073: /**
074: * Return the class the method is a member of.
075: */
076: public Class getDeclaredType() {
077: return declaredType;
078: }
079:
080: /**
081: * Returns the method name.
082: */
083: public String getMethodName() {
084: return methodName;
085: }
086:
087: /**
088: * Returns the method parameter types. Could contain null values
089: * if no type was specified for the corresponding parameter.
090: */
091: public Class[] getParameterTypes() {
092: return parameterTypes;
093: }
094:
095: /**
096: * Returns the keyed method, resolving it if necessary via reflection.
097: */
098: public Method getMethod() throws InvalidMethodKeyException {
099: if (method == null) {
100: method = resolveMethod();
101: }
102: return method;
103: }
104:
105: // internal helpers
106:
107: /**
108: * Resolve the keyed method.
109: */
110: protected Method resolveMethod() throws InvalidMethodKeyException {
111: try {
112: return declaredType.getMethod(methodName, parameterTypes);
113: } catch (NoSuchMethodException e) {
114: Method method = findMethodConsiderAssignableParameterTypes();
115: if (method != null) {
116: return method;
117: } else {
118: throw new InvalidMethodKeyException(this , e);
119: }
120: }
121: }
122:
123: /**
124: * Find the keyed method using 'relaxed' typing.
125: */
126: protected Method findMethodConsiderAssignableParameterTypes() {
127: Method[] candidateMethods = getDeclaredType().getMethods();
128: for (int i = 0; i < candidateMethods.length; i++) {
129: if (candidateMethods[i].getName().equals(methodName)) {
130: // Check if the method has the correct number of parameters.
131: Class[] candidateParameterTypes = candidateMethods[i]
132: .getParameterTypes();
133: if (candidateParameterTypes.length == parameterTypes.length) {
134: int numberOfCorrectArguments = 0;
135: for (int j = 0; j < candidateParameterTypes.length; j++) {
136: // Check if the candidate type is assignable to the sig
137: // parameter type.
138: Class candidateType = candidateParameterTypes[j];
139: Class parameterType = parameterTypes[j];
140: if (parameterType != null) {
141: if (isAssignable(candidateType,
142: parameterType)) {
143: numberOfCorrectArguments++;
144: }
145: } else {
146: // just match on a null param type (effectively 'any')
147: numberOfCorrectArguments++;
148: }
149: }
150: if (numberOfCorrectArguments == parameterTypes.length) {
151: return candidateMethods[i];
152: }
153: }
154: }
155: }
156: return null;
157: }
158:
159: public boolean equals(Object obj) {
160: if (!(obj instanceof MethodKey)) {
161: return false;
162: }
163: MethodKey other = (MethodKey) obj;
164: return declaredType.equals(other.declaredType)
165: && methodName.equals(other.methodName)
166: && parameterTypesEqual(other.parameterTypes);
167: }
168:
169: private boolean parameterTypesEqual(Class[] other) {
170: if (parameterTypes == other) {
171: return true;
172: }
173: if (parameterTypes.length != other.length) {
174: return false;
175: }
176: for (int i = 0; i < this .parameterTypes.length; i++) {
177: if (!ObjectUtils
178: .nullSafeEquals(parameterTypes[i], other[i])) {
179: return false;
180: }
181: }
182: return true;
183: }
184:
185: public int hashCode() {
186: return declaredType.hashCode() + methodName.hashCode()
187: + parameterTypesHash();
188: }
189:
190: private int parameterTypesHash() {
191: if (parameterTypes == null) {
192: return 0;
193: }
194: int hash = 0;
195: for (int i = 0; i < parameterTypes.length; i++) {
196: Class parameterType = parameterTypes[i];
197: if (parameterType != null) {
198: hash += parameterTypes[i].hashCode();
199: }
200: }
201: return hash;
202: }
203:
204: public String toString() {
205: return methodName + "(" + parameterTypesString() + ")";
206: }
207:
208: /**
209: * Convenience method that returns the parameter types describing the
210: * signature of the method as a string.
211: */
212: private String parameterTypesString() {
213: StringBuffer parameterTypesString = new StringBuffer();
214: for (int i = 0; i < parameterTypes.length; i++) {
215: if (parameterTypes[i] == null) {
216: parameterTypesString.append("<any>");
217: } else {
218: parameterTypesString.append(ClassUtils
219: .getShortName(parameterTypes[i]));
220: }
221: if (i < parameterTypes.length - 1) {
222: parameterTypesString.append(',');
223: }
224: }
225: return parameterTypesString.toString();
226: }
227:
228: // internal helpers
229:
230: /**
231: * Determine if the given target type is assignable from the given value
232: * type, assuming setting by reflection. Considers primitive wrapper classes
233: * as assignable to the corresponding primitive types. <p> NOTE: Pulled from
234: * ClassUtils in Spring 2.0 for 1.2.8 compatability.
235: * @param targetType the target type
236: * @param valueType the value type that should be assigned to the target
237: * type
238: * @return if the target type is assignable from the value type
239: */
240: private static boolean isAssignable(Class targetType,
241: Class valueType) {
242: return (targetType.isAssignableFrom(valueType) || targetType
243: .equals(primitiveWrapperTypeMap.get(valueType)));
244: }
245:
246: /**
247: * Map with primitive wrapper type as key and corresponding primitive type
248: * as value, for example: Integer.class -> int.class.
249: */
250: private static final Map primitiveWrapperTypeMap = new HashMap(8);
251:
252: static {
253: primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
254: primitiveWrapperTypeMap.put(Byte.class, byte.class);
255: primitiveWrapperTypeMap.put(Character.class, char.class);
256: primitiveWrapperTypeMap.put(Double.class, double.class);
257: primitiveWrapperTypeMap.put(Float.class, float.class);
258: primitiveWrapperTypeMap.put(Integer.class, int.class);
259: primitiveWrapperTypeMap.put(Long.class, long.class);
260: primitiveWrapperTypeMap.put(Short.class, short.class);
261: }
262: }
|