blob: 2888ce9653294e8411eb41031ca6ce2498dadfc5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.commons.jexl2.internal.introspection;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import org.apache.commons.logging.Log;
/**
* This basic function of this class is to return a Method object for a
* particular class given the name of a method and the parameters to the method
* in the form of an Object[]
* <p/>
* The first time the Introspector sees a class it creates a class method map
* for the class in question. Basically the class method map is a Hastable where
* Method objects are keyed by a concatenation of the method name and the names
* of classes that make up the parameters.
*
* For example, a method with the following signature:
*
* public void method(String a, StringBuffer b)
*
* would be mapped by the key:
*
* "method" + "java.lang.String" + "java.lang.StringBuffer"
*
* This mapping is performed for all the methods in a class and stored for
*
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
* @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
* @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
* @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
* @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
* @version $Id$
* @since 1.0
*/
public class IntrospectorBase {
/** the logger. */
protected final Log rlog;
/**
* Holds the method maps for the classes we know about, keyed by Class.
*/
private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>();
/**
* The class loader used to solve constructors if needed.
*/
private ClassLoader loader;
/**
* Holds the map of classes ctors we know about as well as unknown ones.
*/
private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>();
/**
* Holds the set of classes we have introspected.
*/
private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>();
/**
* Create the introspector.
* @param log the logger to use
*/
public IntrospectorBase(Log log) {
this.rlog = log;
loader = getClass().getClassLoader();
}
/**
* Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>.
*
* @param c Class in which the method search is taking place
* @param key Key of the method being searched for
* @return The desired Method object.
* @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
*
*/
//CSOFF: RedundantThrows
public Method getMethod(Class<?> c, MethodKey key) {
try {
ClassMap classMap = getMap(c);
return classMap.findMethod(key);
} catch (MethodKey.AmbiguousException ae) {
// whoops. Ambiguous. Make a nice log message and return null...
if (rlog != null) {
rlog.error("ambiguous method invocation: "
+ c.getName() + "."
+ key.debugString());
}
}
return null;
}
// CSON: RedundantThrows
/**
* Gets the field named by <code>key</code> for the class <code>c</code>.
*
* @param c Class in which the field search is taking place
* @param key Name of the field being searched for
* @return the desired field or null if it does not exist or is not accessible
* */
public Field getField(Class<?> c, String key) {
ClassMap classMap = getMap(c);
return classMap.findField(c, key);
}
/**
* Gets the array of accessible field names known for a given class.
* @param c the class
* @return the class field names
*/
public String[] getFieldNames(Class<?> c) {
if (c == null) {
return new String[0];
}
ClassMap classMap = getMap(c);
return classMap.getFieldNames();
}
/**
* Gets the array of accessible methods names known for a given class.
* @param c the class
* @return the class method names
*/
public String[] getMethodNames(Class<?> c) {
if (c == null) {
return new String[0];
}
ClassMap classMap = getMap(c);
return classMap.getMethodNames();
}
/**
* A Constructor get cache-miss.
*/
private static class CacheMiss {
/** The constructor used as cache-miss. */
@SuppressWarnings("unused")
public CacheMiss() {}
}
/** The cache-miss marker for the constructors map. */
private static final Constructor<?> CTOR_MISS = CacheMiss.class.getConstructors()[0];
/**
* Sets the class loader used to solve constructors.
* <p>Also cleans the constructors cache.</p>
* @param cloader the class loader; if null, use this instance class loader
*/
public void setLoader(ClassLoader cloader) {
if (cloader == null) {
cloader = getClass().getClassLoader();
}
if (!cloader.equals(loader)) {
synchronized(constructorsMap) {
loader = cloader;
constructorsMap.clear();
constructibleClasses.clear();
}
}
}
/**
* Gets the constructor defined by the <code>MethodKey</code>.
*
* @param key Key of the constructor being searched for
* @return The desired Constructor object.
* @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
*/
public Constructor<?> getConstructor(final MethodKey key) {
return getConstructor(null, key);
}
/**
* Gets the constructor defined by the <code>MethodKey</code>.
* @param c the class we want to instantiate
* @param key Key of the constructor being searched for
* @return The desired Constructor object.
* @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
*/
//CSOFF: RedundantThrows
public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) {
try {
Constructor<?> ctor = null;
synchronized(constructorsMap) {
ctor = constructorsMap.get(key);
// that's a clear miss
if (CTOR_MISS.equals(ctor)) {
return null;
}
// let's introspect...
if (ctor == null) {
final String cname = key.getMethod();
// do we know about this class?
Class<?> clazz = constructibleClasses.get(cname);
try {
// do find the most specific ctor
if (clazz == null) {
if (c != null && c.getName().equals(key.getMethod())) {
clazz = c;
} else {
clazz = loader.loadClass(cname);
}
// add it to list of known loaded classes
constructibleClasses.put(cname, clazz);
}
List<Constructor<?>> l = new LinkedList<Constructor<?>>();
for(Constructor<?> ictor : clazz.getConstructors()) {
l.add(ictor);
}
// try to find one
ctor = key.getMostSpecificConstructor(l);
if (ctor != null) {
constructorsMap.put(key, ctor);
} else {
constructorsMap.put(key, CTOR_MISS);
}
} catch(ClassNotFoundException xnotfound) {
if (rlog.isDebugEnabled()) {
rlog.debug("could not load class " + cname, xnotfound);
}
ctor = null;
} catch(MethodKey.AmbiguousException xambiguous) {
rlog.warn("ambiguous ctor detected for " + cname, xambiguous);
ctor = null;
}
}
}
return ctor;
} catch (MethodKey.AmbiguousException ae) {
// whoops. Ambiguous. Make a nice log message and return null...
if (rlog != null) {
rlog.error("ambiguous constructor invocation: new "
+ key.debugString());
}
}
return null;
}
// CSON: RedundantThrows
/**
* Gets the ClassMap for a given class.
* @param c the class
* @return the class map
*/
private ClassMap getMap(Class<?> c) {
synchronized (classMethodMaps) {
ClassMap classMap = classMethodMaps.get(c);
if (classMap == null) {
classMap = new ClassMap(c,rlog);
classMethodMaps.put(c, classMap);
}
return classMap;
}
}
}