/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* 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.apache.axis.utils.cache;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.axis.utils.ClassUtils;
/**
* A cache for methods.
* Used to get methods by their signature and stores them in a local
* cache for performance reasons.
* This class is a singleton - so use getInstance to get an instance of it.
*
* @author Davanum Srinivas <dims@yahoo.com>
* @author Sebastian Dietrich <sebastian.dietrich@anecon.com>
*/
public class MethodCache {
/**
* The only instance of this class
*/
transient private static MethodCache instance;
/**
* Cache for Methods
* In fact this is a map (with classes as keys) of a map (with method-names as keys)
*/
transient private static ThreadLocal cache;
/**
* The <i>private</i> constructor for this class.
* Use getInstance to get an instance (the only one).
*/
private MethodCache() {
cache = new ThreadLocal();
}
/**
* Gets the only instance of this class
* @return the only instance of this class
*/
public static MethodCache getInstance() {
if (instance == null) {
instance = new MethodCache();
}
return instance;
}
/**
* Returns the per thread hashmap (for method caching)
*/
private Map getMethodCache() {
Map map = (Map) cache.get();
if (map == null) {
map = new HashMap();
cache.set(map);
}
return map;
}
/**
* Class used as the key for the method cache table.
*
*/
static class MethodKey {
/** the name of the method in the cache */
private final String methodName;
/** the list of types accepted by the method as arguments */
private final Class[] parameterTypes;
/**
* Creates a new <code>MethodKey</code> instance.
*
* @param methodName a <code>String</code> value
* @param parameterTypes a <code>Class[]</code> value
*/
MethodKey(String methodName, Class[] parameterTypes) {
this.methodName = methodName;
this.parameterTypes = parameterTypes;
}
public boolean equals(Object other) {
MethodKey that = (MethodKey) other;
return this.methodName.equals(that.methodName)
&& Arrays.equals(this.parameterTypes,
that.parameterTypes);
}
public int hashCode() {
// allow overloaded methods to collide; we'll sort it out
// in equals(). Note that String's implementation of
// hashCode already caches its value, so there's no point
// in doing so here.
return methodName.hashCode();
}
}
/** used to track methods we've sought but not found in the past */
private static final Object NULL_OBJECT = new Object();
/**
* Returns the specified method - if any.
*
* @param clazz the class to get the method from
* @param methodName the name of the method
* @param parameterTypes the parameters of the method
* @return the found method
*
* @throws NoSuchMethodException if the method can't be found
*/
public Method getMethod(Class clazz,
String methodName,
Class[] parameterTypes)
throws NoSuchMethodException {
String className = clazz.getName();
Map cache = getMethodCache();
Method method = null;
Map methods = null;
// Strategy is as follows:
// construct a MethodKey to represent the name/arguments
// of a method's signature.
//
// use the name of the class to retrieve a map of that
// class' methods
//
// if a map exists, use the MethodKey to find the
// associated value object. if that object is a Method
// instance, return it. if that object is anything
// else, then it's a reference to our NULL_OBJECT
// instance, indicating that we've previously tried
// and failed to find a method meeting our requirements,
// so return null
//
// if the map of methods for the class doesn't exist,
// or if no value is associated with our key in that map,
// then we perform a reflection-based search for the method.
//
// if we find a method in that search, we store it in the
// map using the key; otherwise, we store a reference
// to NULL_OBJECT using the key.
// Check the cache first.
MethodKey key = new MethodKey(methodName, parameterTypes);
methods = (Map) cache.get(clazz);
if (methods != null) {
Object o = methods.get(key);
if (o != null) { // cache hit
if (o instanceof Method) { // good cache hit
return (Method) o;
} else { // bad cache hit
// we hit the NULL_OBJECT, so this is a search
// that previously failed; no point in doing
// it again as it is a worst case search
// through the entire classpath.
return null;
}
} else {
// cache miss: fall through to reflective search
}
} else {
// cache miss: fall through to reflective search
}
try {
method = clazz.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e1) {
if (!clazz.isPrimitive() && !className.startsWith("java.") && !className.startsWith("javax.")) {
try {
Class helper = ClassUtils.forName(className + "_Helper");
method = helper.getMethod(methodName, parameterTypes);
} catch (ClassNotFoundException e2) {
}
}
}
// first time we've seen this class: set up its method cache
if (methods == null) {
methods = new HashMap();
cache.put(clazz, methods);
}
// when no method is found, cache the NULL_OBJECT
// so that we don't have to repeat worst-case searches
// every time.
if (null == method) {
methods.put(key, NULL_OBJECT);
} else {
methods.put(key, method);
}
return method;
}
}
|