/* * 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 * @author Sebastian Dietrich */ 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 private 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 MethodKey instance. * * @param methodName a String value * @param parameterTypes a Class[] 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; } }