diff options
author | Tom Tromey <tromey@gcc.gnu.org> | 2005-07-16 00:30:23 +0000 |
---|---|---|
committer | Tom Tromey <tromey@gcc.gnu.org> | 2005-07-16 00:30:23 +0000 |
commit | f911ba985aa7fe0096c386c5be385ac5825ea527 (patch) | |
tree | a0b991cf5866ae1d616639b906ac001811d74508 /libjava/classpath/java/beans/Introspector.java | |
parent | 6f4434b39b261de5317dc81ddfdd94d2e1d62b11 (diff) | |
download | gcc-f911ba985aa7fe0096c386c5be385ac5825ea527.zip gcc-f911ba985aa7fe0096c386c5be385ac5825ea527.tar.gz gcc-f911ba985aa7fe0096c386c5be385ac5825ea527.tar.bz2 |
Initial revision
From-SVN: r102074
Diffstat (limited to 'libjava/classpath/java/beans/Introspector.java')
-rw-r--r-- | libjava/classpath/java/beans/Introspector.java | 608 |
1 files changed, 608 insertions, 0 deletions
diff --git a/libjava/classpath/java/beans/Introspector.java b/libjava/classpath/java/beans/Introspector.java new file mode 100644 index 0000000..02781b4 --- /dev/null +++ b/libjava/classpath/java/beans/Introspector.java @@ -0,0 +1,608 @@ +/* java.beans.Introspector + Copyright (C) 1998, 2002, 2003 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.beans; + +import gnu.java.beans.BeanInfoEmbryo; +import gnu.java.beans.ExplicitBeanInfo; +import gnu.java.beans.IntrospectionIncubator; +import gnu.java.lang.ClassHelper; + +import java.util.Hashtable; +import java.util.Vector; + +/** + * Introspector is the class that does the bulk of the + * design-time work in Java Beans. Every class must have + * a BeanInfo in order for an RAD tool to use it; but, as + * promised, you don't have to write the BeanInfo class + * yourself if you don't want to. All you have to do is + * call getBeanInfo() in the Introspector and it will use + * standard JavaBeans-defined method signatures to + * determine the information about your class.<P> + * + * Don't worry about it too much, though: you can provide + * JavaBeans with as much customized information as you + * want, or as little as you want, using the BeanInfo + * interface (see BeanInfo for details).<P> + * + * <STRONG>Order of Operations</STRONG><P> + * + * When you call getBeanInfo(class c), the Introspector + * first searches for BeanInfo class to see if you + * provided any explicit information. It searches for a + * class named <bean class name>BeanInfo in different + * packages, first searching the bean class's package + * and then moving on to search the beanInfoSearchPath.<P> + * + * If it does not find a BeanInfo class, it acts as though + * it had found a BeanInfo class returning null from all + * methods (meaning it should discover everything through + * Introspection). If it does, then it takes the + * information it finds in the BeanInfo class to be + * canonical (that is, the information speaks for its + * class as well as all superclasses).<P> + * + * When it has introspected the class, calls + * getBeanInfo(c.getSuperclass) and adds that information + * to the information it has, not adding to any information + * it already has that is canonical.<P> + * + * <STRONG>Introspection Design Patterns</STRONG><P> + * + * When the Introspector goes in to read the class, it + * follows a well-defined order in order to not leave any + * methods unaccounted for. Its job is to step over all + * of the public methods in a class and determine whether + * they are part of a property, an event, or a method (in + * that order). + * + * + * <STRONG>Properties:</STRONG><P> + * + * <OL> + * <LI>If there is a <CODE>public boolean isXXX()</CODE> + * method, then XXX is a read-only boolean property. + * <CODE>boolean getXXX()</CODE> may be supplied in + * addition to this method, although isXXX() is the + * one that will be used in this case and getXXX() + * will be ignored. If there is a + * <CODE>public void setXXX(boolean)</CODE> method, + * it is part of this group and makes it a read-write + * property.</LI> + * <LI>If there is a + * <CODE>public <type> getXXX(int)</CODE> + * method, then XXX is a read-only indexed property of + * type <type>. If there is a + * <CODE>public void setXXX(int,<type>)</CODE> + * method, then it is a read-write indexed property of + * type <type>. There may also be a + * <CODE>public <type>[] getXXX()</CODE> and a + * <CODE>public void setXXX(<type>)</CODE> + * method as well.</LI> + * <LI>If there is a + * <CODE>public void setXXX(int,<type>)</CODE> + * method, then it is a write-only indexed property of + * type <type>. There may also be a + * <CODE>public <type>[] getXXX()</CODE> and a + * <CODE>public void setXXX(<type>)</CODE> + * method as well.</LI> + * <LI>If there is a + * <CODE>public <type> getXXX()</CODE> method, + * then XXX is a read-only property of type + * <type>. If there is a + * <CODE>public void setXXX(<type>)</CODE> + * method, then it will be used for the property and + * the property will be considered read-write.</LI> + * <LI>If there is a + * <CODE>public void setXXX(<type>)</CODE> + * method, then as long as XXX is not already used as + * the name of a property, XXX is assumed to be a + * write-only property of type <type>.</LI> + * <LI>In all of the above cases, if the setXXX() method + * throws <CODE>PropertyVetoException</CODE>, then the + * property in question is assumed to be constrained. + * No properties are ever assumed to be bound + * (<STRONG>Spec Note:</STRONG> this is not in the + * spec, it just makes sense). See PropertyDescriptor + * for a description of bound and constrained + * properties.</LI> + * </OL> + * + * <STRONG>Events:</STRONG><P> + * + * If there is a pair of methods, + * <CODE>public void addXXX(<type>)</CODE> and + * <CODE>public void removeXXX(<type>)</CODE>, where + * <type> is a descendant of + * <CODE>java.util.EventListener</CODE>, then the pair of + * methods imply that this Bean will fire events to + * listeners of type <type>.<P> + * + * If the addXXX() method throws + * <CODE>java.util.TooManyListenersException</CODE>, then + * the event set is assumed to be <EM>unicast</EM>. See + * EventSetDescriptor for a discussion of unicast event + * sets.<P> + * + * <STRONG>Spec Note:</STRONG> the spec seems to say that + * the listener type's classname must be equal to the XXX + * part of addXXX() and removeXXX(), but that is not the + * case in Sun's implementation, so I am assuming it is + * not the case in general.<P> + * + * <STRONG>Methods:</STRONG><P> + * + * Any public methods (including those which were used + * for Properties or Events) are used as Methods. + * + * @author John Keiser + * @since JDK1.1 + * @see java.beans.BeanInfo + */ +public class Introspector { + + public static final int USE_ALL_BEANINFO = 1; + public static final int IGNORE_IMMEDIATE_BEANINFO = 2; + public static final int IGNORE_ALL_BEANINFO = 3; + + static String[] beanInfoSearchPath = {"gnu.java.beans.info"}; + static Hashtable beanInfoCache = new Hashtable(); + + private Introspector() {} + + /** + * Get the BeanInfo for class <CODE>beanClass</CODE>, + * first by looking for explicit information, next by + * using standard design patterns to determine + * information about the class. + * + * @param beanClass the class to get BeanInfo about. + * @return the BeanInfo object representing the class. + */ + public static BeanInfo getBeanInfo(Class beanClass) + throws IntrospectionException + { + BeanInfo cachedInfo; + synchronized(beanClass) + { + cachedInfo = (BeanInfo)beanInfoCache.get(beanClass); + if(cachedInfo != null) + { + return cachedInfo; + } + cachedInfo = getBeanInfo(beanClass,null); + beanInfoCache.put(beanClass,cachedInfo); + return cachedInfo; + } + } + + /** + * Flush all of the Introspector's internal caches. + * + * @since 1.2 + */ + public static void flushCaches() + { + beanInfoCache.clear(); + + // Clears all the intermediate ExplicitInfo instances which + // have been created. + // This makes sure we have to retrieve stuff like BeanDescriptors + // again. (Remember that FeatureDescriptor can be modified by the user.) + ExplicitInfo.flushCaches(); + } + + /** + * Flush the Introspector's internal cached information for a given + * class. + * + * @param clz the class to be flushed. + * @throws NullPointerException if clz is null. + * @since 1.2 + */ + public static void flushFromCaches(Class clz) + { + synchronized (clz) + { + beanInfoCache.remove(clz); + } + } + + /** + * Get the BeanInfo for class <CODE>beanClass</CODE>, + * first by looking for explicit information, next by + * using standard design patterns to determine + * information about the class. It crawls up the + * inheritance tree until it hits <CODE>topClass</CODE>. + * + * @param beanClass the Bean class. + * @param stopClass the class to stop at. + * @return the BeanInfo object representing the class. + */ + public static BeanInfo getBeanInfo(Class beanClass, Class stopClass) + throws IntrospectionException + { + ExplicitInfo explicit = new ExplicitInfo(beanClass, stopClass); + + IntrospectionIncubator ii = new IntrospectionIncubator(); + ii.setPropertyStopClass(explicit.propertyStopClass); + ii.setEventStopClass(explicit.eventStopClass); + ii.setMethodStopClass(explicit.methodStopClass); + ii.addMethods(beanClass.getMethods()); + + BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo(); + PropertyDescriptor[] p = explicit.explicitPropertyDescriptors; + if(p!=null) + { + for(int i=0;i<p.length;i++) + { + if(!currentInfo.hasProperty(p[i])) + { + currentInfo.addProperty(p[i]); + } + } + if(explicit.defaultProperty != -1) + { + currentInfo.setDefaultPropertyName(p[explicit.defaultProperty].getName()); + } + } + EventSetDescriptor[] e = explicit.explicitEventSetDescriptors; + if(e!=null) + { + for(int i=0;i<e.length;i++) + { + if(!currentInfo.hasEvent(e[i])) + { + currentInfo.addEvent(e[i]); + } + } + if(explicit.defaultEvent != -1) + { + currentInfo.setDefaultEventName(e[explicit.defaultEvent].getName()); + } + } + MethodDescriptor[] m = explicit.explicitMethodDescriptors; + if(m!=null) + { + for(int i=0;i<m.length;i++) + { + if(!currentInfo.hasMethod(m[i])) + { + currentInfo.addMethod(m[i]); + } + } + } + + // Sets the info's BeanDescriptor to the one we extracted from the + // explicit BeanInfo instance(s) if they contained one. Otherwise we + // create the BeanDescriptor from scratch. + // Note: We do not create a copy the retrieved BeanDescriptor which will allow + // the user to modify the instance while it is cached. However this is how + // the RI does it. + currentInfo.setBeanDescriptor( + (explicit.explicitBeanDescriptor == null ? + new BeanDescriptor(beanClass, null) : + explicit.explicitBeanDescriptor)); + + currentInfo.setAdditionalBeanInfo(explicit.explicitBeanInfo); + currentInfo.setIcons(explicit.im); + + return currentInfo.getBeanInfo(); + } + + /** + * Get the search path for BeanInfo classes. + * + * @return the BeanInfo search path. + */ + public static String[] getBeanInfoSearchPath() + { + return beanInfoSearchPath; + } + + /** + * Set the search path for BeanInfo classes. + * @param beanInfoSearchPath the new BeanInfo search + * path. + */ + public static void setBeanInfoSearchPath(String[] beanInfoSearchPath) + { + Introspector.beanInfoSearchPath = beanInfoSearchPath; + } + + /** + * A helper method to convert a name to standard Java + * naming conventions: anything with two capitals as the + * first two letters remains the same, otherwise the + * first letter is decapitalized. URL = URL, I = i, + * MyMethod = myMethod. + * + * @param name the name to decapitalize. + * @return the decapitalized name. + */ + public static String decapitalize(String name) + { + try + { + if(!Character.isUpperCase(name.charAt(0))) + { + return name; + } + else + { + try + { + if(Character.isUpperCase(name.charAt(1))) + { + return name; + } + else + { + char[] c = name.toCharArray(); + c[0] = Character.toLowerCase(c[0]); + return new String(c); + } + } + catch(StringIndexOutOfBoundsException E) + { + char[] c = new char[1]; + c[0] = Character.toLowerCase(name.charAt(0)); + return new String(c); + } + } + } + catch(StringIndexOutOfBoundsException E) + { + return name; + } + catch(NullPointerException E) + { + return null; + } + } + + static BeanInfo copyBeanInfo(BeanInfo b) + { + java.awt.Image[] icons = new java.awt.Image[4]; + for(int i=1;i<=4;i++) + { + icons[i-1] = b.getIcon(i); + } + + return new ExplicitBeanInfo(b.getBeanDescriptor(), + b.getAdditionalBeanInfo(), + b.getPropertyDescriptors(), + b.getDefaultPropertyIndex(), + b.getEventSetDescriptors(), + b.getDefaultEventIndex(), + b.getMethodDescriptors(), + icons); + } +} + +class ExplicitInfo +{ + BeanDescriptor explicitBeanDescriptor; + BeanInfo[] explicitBeanInfo; + + PropertyDescriptor[] explicitPropertyDescriptors; + EventSetDescriptor[] explicitEventSetDescriptors; + MethodDescriptor[] explicitMethodDescriptors; + + int defaultProperty; + int defaultEvent; + + java.awt.Image[] im = new java.awt.Image[4]; + + Class propertyStopClass; + Class eventStopClass; + Class methodStopClass; + + static Hashtable explicitBeanInfos = new Hashtable(); + static Vector emptyBeanInfos = new Vector(); + + ExplicitInfo(Class beanClass, Class stopClass) + { + while(beanClass != null && !beanClass.equals(stopClass)) + { + + BeanInfo explicit = findExplicitBeanInfo(beanClass); + + + if(explicit != null) + { + + if(explicitBeanDescriptor == null) + { + explicitBeanDescriptor = explicit.getBeanDescriptor(); + } + + if(explicitBeanInfo == null) + { + explicitBeanInfo = explicit.getAdditionalBeanInfo(); + } + + if(explicitPropertyDescriptors == null) + { + if(explicit.getPropertyDescriptors() != null) + { + explicitPropertyDescriptors = explicit.getPropertyDescriptors(); + defaultProperty = explicit.getDefaultPropertyIndex(); + propertyStopClass = beanClass; + } + } + + if(explicitEventSetDescriptors == null) + { + if(explicit.getEventSetDescriptors() != null) + { + explicitEventSetDescriptors = explicit.getEventSetDescriptors(); + defaultEvent = explicit.getDefaultEventIndex(); + eventStopClass = beanClass; + } + } + + if(explicitMethodDescriptors == null) + { + if(explicit.getMethodDescriptors() != null) + { + explicitMethodDescriptors = explicit.getMethodDescriptors(); + methodStopClass = beanClass; + } + } + + if(im[0] == null && im[1] == null + && im[2] == null && im[3] == null) + { + im[0] = explicit.getIcon(0); + im[1] = explicit.getIcon(1); + im[2] = explicit.getIcon(2); + im[3] = explicit.getIcon(3); + } + } + beanClass = beanClass.getSuperclass(); + } + + if(propertyStopClass == null) + { + propertyStopClass = stopClass; + } + + if(eventStopClass == null) + { + eventStopClass = stopClass; + } + + if(methodStopClass == null) + { + methodStopClass = stopClass; + } + } + + /** Throws away all cached data and makes sure we re-instantiate things + * like BeanDescriptors again. + */ + static void flushCaches() { + explicitBeanInfos.clear(); + emptyBeanInfos.clear(); + } + + static BeanInfo findExplicitBeanInfo(Class beanClass) + { + BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass); + if(retval != null) + { + return retval; + } + else if(emptyBeanInfos.indexOf(beanClass) != -1) + { + return null; + } + else + { + retval = reallyFindExplicitBeanInfo(beanClass); + if(retval != null) + { + explicitBeanInfos.put(beanClass,retval); + } + else + { + emptyBeanInfos.addElement(beanClass); + } + return retval; + } + } + + static BeanInfo reallyFindExplicitBeanInfo(Class beanClass) + { + ClassLoader beanClassLoader = beanClass.getClassLoader(); + BeanInfo beanInfo; + + beanInfo = getBeanInfo(beanClassLoader, beanClass.getName() + "BeanInfo"); + if (beanInfo == null) + { + String newName; + newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo"; + + for(int i = 0; i < Introspector.beanInfoSearchPath.length; i++) + { + if (Introspector.beanInfoSearchPath[i].equals("")) + beanInfo = getBeanInfo(beanClassLoader, newName); + else + beanInfo = getBeanInfo(beanClassLoader, + Introspector.beanInfoSearchPath[i] + "." + + newName); + + // Returns the beanInfo if it exists and the described class matches + // the one we searched. + if (beanInfo != null && beanInfo.getBeanDescriptor() != null && + beanInfo.getBeanDescriptor().getBeanClass() == beanClass) + + return beanInfo; + } + } + + return beanInfo; + } + + /** + * Returns an instance of the given class name when it can be loaded + * through the given class loader, or null otherwise. + */ + private static BeanInfo getBeanInfo(ClassLoader cl, String infoName) + { + try + { + return (BeanInfo) Class.forName(infoName, true, cl).newInstance(); + } + catch (ClassNotFoundException cnfe) + { + return null; + } + catch (IllegalAccessException iae) + { + return null; + } + catch (InstantiationException ie) + { + return null; + } + } + +} |