// ClassInfo.java // FESI Copyright (c) Jean-Marc Lugrin, 1999 // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package FESI.Interpreter; import java.util.Hashtable; import java.util.Vector; import java.lang.reflect.*; import java.beans.*; import FESI.Data.*; import FESI.Exceptions.*; /** * This class contains the static routines to access the cache of class information * as well as the ClassInfo (class specific cached information). The information * is separated between the bean based information and the low level information. * Even if most classes will have the same information in both cases, they are * likely to be used in only one way, so there space overhead is ususally small. *
The cache is filled in a lazy manner, only requested information is cached, * even if this requires more sweeps of the low level information. The rational is * that for small set of information this is not a major drawback, and for large * set of information (for example the list of methods of an AWT component), only * a small subset of it is really used. So the small foorprint is given more importance * than a faster access for seldom cases. *
The cache is shared by all instance of the evaluator and class loaders (it is * enough to identify the classes by their Class object). So the access must * be synchronized and no evaluator specific information must be added. *
The existence of this cache defeats class garbage collections for classes
* accessed via EcmaScript. This should not be too severe a restriction, and can
* be lifted in JDK 1.2.
*/
public class ClassInfo {
/** Cache of information on all classes, using the class object as a key */
static private Hashtable allClassInfo = new Hashtable();
/** Cache of public methods */
private Hashtable publicMethods = null;
/** Cache of bean methods */
private Hashtable beanMethods = null;
/** Cache of bean properties */
private Hashtable beanProperties = null;
/** Cache of BeanInofo */
private BeanInfo beanInfo = null;
/**
* Ensure that ClassInfo objects can only be created via
* the factory.
*/
private ClassInfo() {
}
/**
* Ensure that the specified class has a ClassInfo object in the
* cached. Create an empty one if needed. Must be called synchronized.
*
* @param cls The class for which we look for a ClassInfo
* @return the ClassInfo of cls, added to the cache if needed
*/
private static ClassInfo ensureClassInfo(Class cls) {
boolean debug = ESLoader.isDebugJavaAccess();
ClassInfo classInfo = (ClassInfo) allClassInfo.get(cls);
if (classInfo == null) {
if (debug) System.out.println("** Class info for class '" +
cls + "' not found in cache, created");
classInfo = new ClassInfo();
allClassInfo.put(cls, classInfo);
}
return classInfo;
}
/**
* Get the property descriptor for the specified field of the specified class,
* considered as a bean.
*
* @param fieldName The name of the property
* @param cls The class for which we look for the property.
* @return The PropertyDescriptor or null if not found or in case of error
*/
synchronized public static PropertyDescriptor lookupBeanField(String fieldName, Class cls) {
ClassInfo classInfo = ClassInfo.ensureClassInfo(cls);
return classInfo.cachedBeanFieldLookup(fieldName, cls);
}
/**
* Get the property descriptor for the specified field of the specified class,
* considered as a bean, in the current ClassInfo cache. Add it to the cache
* if needed, after veryfying that the accessors are OK.
*
* @param fieldName The name of the property
* @param cls The class for which we look for the property.
* @return The PropertyDescriptor or null if not found or in case of error
*/
private PropertyDescriptor cachedBeanFieldLookup(String propertyName, Class cls) {
boolean debug = ESLoader.isDebugJavaAccess();
// Check that there is a bean properties cache, chech if the property was cached
if (beanProperties != null) {
if (debug) System.out.println("** Bean properties for class '" +
cls + "' found in cache");
PropertyDescriptor descriptor =
(PropertyDescriptor) beanProperties.get(propertyName);
if (descriptor!= null) {
if (debug) System.out.println("** property descriptor '" + propertyName + "' found in cache");
return descriptor;
}
}
// Not in cache
if (debug) System.out.println("** No property named '" +
propertyName + "' found in cache, lookup started");
// Do we have a cached BeanInfo ? create it if no
if (beanInfo == null) {
try {
beanInfo = Introspector.getBeanInfo(cls);
} catch (IntrospectionException e) {
if (debug) System.out.println(" ** Error getting beaninfo: " + e);
return null;
}
}
// Get the property descriptor by name
PropertyDescriptor [] allProperties = beanInfo.getPropertyDescriptors();
PropertyDescriptor descriptor = null; // none found
for (int i=0; i If a method was not found, the cache is not modified, and a new
* slow lookup will be done on next search. The case is rare enought
* that it is better to keep the code simpler.
*
* @param functionName The name of the function
* @param cls The class in which the function is defined
* @return The list of methods or null in case of error or if none found.
*/
private Method [] cachedPublicMethodLookup(String functionName, Class cls) throws EcmaScriptException {
boolean debug = ESLoader.isDebugJavaAccess();
if (publicMethods != null) {
if (debug) System.out.println("** Method descriptor for class '" +
cls + "' found in cache");
Method [] methods = (Method []) publicMethods.get(functionName);
if (methods!= null) {
if (debug) System.out.println("** " + methods.length +
" method(s) named '" + functionName + "' found in cache");
return methods;
}
}
// Not in cache, find if any matching the same name can be found
if (debug) System.out.println("** No method named '" +
functionName + "' found in class cache, lookup started");
Method [] allMethods = cls.getMethods();
Vector methodVector = new Vector(allMethods.length);
boolean wasFound = false;
for (int i=0; i Only the public variant of a method is returned.
* If a method was not found, the cache is not modified, and a new
* slow lookup will be done on next search. The case is rare enought
* that it is better to keep the code simpler.
*
* @param functionName The name of the function
* @param cls The class in which the function is defined
* @return The list of methods or null in case of error or if none found.
*/
private Method [] cachedBeanMethodLookup(String functionName, Class cls) {
boolean debug = ESLoader.isDebugJavaAccess();
if (beanMethods != null) {
if (debug) System.out.println("** Method descriptor for bean '" +
cls + "' found in cache");
Method [] methods = (Method []) beanMethods.get(functionName);
if (methods!= null) {
if (debug) System.out.println("** " + methods.length +
" method(s) named '" + functionName + "' found in cache");
return methods;
}
}
// Not in cache, find if any matching the same name can be found
if (debug) System.out.println("** No method named '" +
functionName + "' found in bean cache, lookup started");
// Do we have a cached BeanInfo ? create it if no
if (beanInfo == null) {
try {
beanInfo = Introspector.getBeanInfo(cls);
} catch (IntrospectionException e) {
if (debug) System.out.println(" ** Error getting beaninfo: " + e);
return null;
}
}
MethodDescriptor [] allDescriptors = beanInfo.getMethodDescriptors();
Vector methodVector = new Vector(allDescriptors.length);
for (int i=0; i