home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / beans / Introspector.java < prev    next >
Encoding:
Java Source  |  1998-03-20  |  34.5 KB  |  1,073 lines

  1. /*
  2.  * @(#)Introspector.java    1.82 98/03/18
  3.  *
  4.  * Copyright 1996, 1997 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  *
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. package java.beans;
  16.  
  17. import java.lang.reflect.*;
  18.  
  19. /**
  20.  * The Introspector class provides a standard way for tools to learn about
  21.  * the properties, events, and methods supported by a target Java Bean.
  22.  * <p>
  23.  * For each of those three kinds of information, the Introspector will
  24.  * separately analyze the bean's class and superclasses looking for
  25.  * either explicit or implicit information and use that information to
  26.  * build a BeanInfo object that comprehensively describes the target bean.
  27.  * <p>
  28.  * For each class "Foo", explicit information may be available if there exists
  29.  * a corresponding "FooBeanInfo" class that provides a non-null value when
  30.  * queried for the information.   We first look for the BeanInfo class by
  31.  * taking the full package-qualified name of the target bean class and
  32.  * appending "BeanInfo" to form a new class name.  If this fails, then
  33.  * we take the final classname component of this name, and look for that
  34.  * class in each of the packages specified in the BeanInfo package search
  35.  * path.
  36.  * <p>
  37.  * Thus for a class such as "sun.xyz.OurButton" we would first look for a
  38.  * BeanInfo class called "sun.xyz.OurButtonBeanInfo" and if that failed we'd
  39.  * look in each package in the BeanInfo search path for an OurButtonBeanInfo
  40.  * class.  With the default search path, this would mean looking for
  41.  * "sun.beans.infos.OurButtonBeanInfo".
  42.  * <p>
  43.  * If a class provides explicit BeanInfo about itself then we add that to
  44.  * the BeanInfo information we obtained from analyzing any derived classes,
  45.  * but we regard the explicit information as being definitive for the current
  46.  * class and its base classes, and do not proceed any further up the superclass
  47.  * chain.
  48.  * <p>
  49.  * If we don't find explicit BeanInfo on a class, we use low-level
  50.  * reflection to study the methods of the class and apply standard design
  51.  * patterns to identify property accessors, event sources, or public
  52.  * methods.  We then proceed to analyze the class's superclass and add
  53.  * in the information from it (and possibly on up the superclass chain).
  54.  */
  55.  
  56. public class Introspector {
  57.  
  58.     // Flags that can be used to control getBeanInfo:
  59.     public final static int USE_ALL_BEANINFO           = 1;
  60.     public final static int IGNORE_IMMEDIATE_BEANINFO  = 2;
  61.     public final static int IGNORE_ALL_BEANINFO        = 3;
  62.  
  63.     //======================================================================
  64.     //                 Public methods
  65.     //======================================================================
  66.  
  67.     /**
  68.      * Introspect on a Java bean and learn about all its properties, exposed
  69.      * methods, and events.
  70.      *
  71.      * @param beanClass  The bean class to be analyzed.
  72.      * @return  A BeanInfo object describing the target bean.
  73.      * @exception IntrospectionException if an exception occurs during
  74.      *              introspection.
  75.      */
  76.     public static BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException {
  77.     GenericBeanInfo bi = (GenericBeanInfo)beanInfoCache.get(beanClass);
  78.     if (bi == null) {
  79.         bi = (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
  80.         beanInfoCache.put(beanClass, bi);
  81.     }
  82.  
  83.     // Make an independent copy of the BeanInfo.
  84.     return new GenericBeanInfo(bi);
  85.     }
  86.  
  87.     /**
  88.      * Introspect on a Java bean and learn about all its properties, exposed
  89.      * methods, and events, subnject to some comtrol flags.
  90.      *
  91.      * @param beanClass  The bean class to be analyzed.
  92.      * @param flags  Flags to control the introspection.
  93.      *     If flags == USE_ALL_BEANINFO then we use all of the BeanInfo
  94.      *         classes we can discover.
  95.      *     If flags == IGNORE_IMMEDIATE_BEANINFO then we ignore any
  96.      *           BeanInfo associated with the specified beanClass.
  97.      *     If flags == IGNORE_ALL_BEANINFO then we ignore all BeanInfo
  98.      *           associated with the specified beanClass or any of its
  99.      *         parent classes.
  100.      * @return  A BeanInfo object describing the target bean.
  101.      * @exception IntrospectionException if an exception occurs during
  102.      *              introspection.
  103.      */
  104.     public static BeanInfo getBeanInfo(Class beanClass, int flags)
  105.                         throws IntrospectionException {
  106.     // We don't use the cache.
  107.     GenericBeanInfo bi = (new Introspector(beanClass, null, flags)).getBeanInfo();
  108.  
  109.     // Make an independent copy of the BeanInfo.
  110.     return new GenericBeanInfo(bi);
  111.     }
  112.  
  113.     /**
  114.      * Introspect on a Java bean and learn all about its properties, exposed
  115.      * methods, below a given "stop" point.
  116.      *
  117.      * @param bean The bean class to be analyzed.
  118.      * @param stopClass The baseclass at which to stop the analysis.  Any
  119.      *    methods/properties/events in the stopClass or in its baseclasses
  120.      *    will be ignored in the analysis.
  121.      * @exception IntrospectionException if an exception occurs during
  122.      *              introspection.
  123.      */
  124.     public static BeanInfo getBeanInfo(Class beanClass,    Class stopClass)
  125.                         throws IntrospectionException {
  126.     GenericBeanInfo bi = (new Introspector(beanClass, stopClass, USE_ALL_BEANINFO)).getBeanInfo();
  127.  
  128.     // Make an independent copy of the BeanInfo.
  129.     return new GenericBeanInfo(bi);
  130.     }
  131.  
  132.     /**
  133.      * Utility method to take a string and convert it to normal Java variable
  134.      * name capitalization.  This normally means converting the first
  135.      * character from upper case to lower case, but in the (unusual) special
  136.      * case when there is more than one character and both the first and
  137.      * second characters are upper case, we leave it alone.
  138.      * <p>
  139.      * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
  140.      * as "URL".
  141.      *
  142.      * @param  name The string to be decapitalized.
  143.      * @return  The decapitalized version of the string.
  144.      */
  145.     public static String decapitalize(String name) {
  146.     if (name == null || name.length() == 0) {
  147.         return name;
  148.     }
  149.     if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
  150.             Character.isUpperCase(name.charAt(0))){
  151.         return name;
  152.     }
  153.     char chars[] = name.toCharArray();
  154.     chars[0] = Character.toLowerCase(chars[0]);
  155.     return new String(chars);
  156.     }
  157.  
  158.     /**
  159.      * @return  The array of package names that will be searched in
  160.      *        order to find BeanInfo classes.
  161.      * <p>     This is initially set to {"sun.beans.infos"}.
  162.      */
  163.  
  164.     public static String[] getBeanInfoSearchPath() {
  165.     return searchPath;
  166.     }
  167.  
  168.     /**
  169.      * Change the list of package names that will be used for
  170.      *        finding BeanInfo classes.
  171.      * @param path  Array of package names.
  172.      */
  173.  
  174.     public static void setBeanInfoSearchPath(String path[]) {
  175.     searchPath = path;
  176.     }
  177.  
  178.  
  179.     /**
  180.      * Flush all of the Introspector's internal caches.  This method is
  181.      * not normally required.  It is normally only needed by advanced
  182.      * tools that update existing "Class" objects in-place and need
  183.      * to make the Introspector re-analyze existing Class objects.
  184.      */
  185.  
  186.     public static void flushCaches() {
  187.     beanInfoCache.clear();
  188.     declaredMethodCache.clear();
  189.     }
  190.  
  191.     /**
  192.      * Flush the Introspector's internal cached information for a given class.
  193.      * This method is not normally required.  It is normally only needed
  194.      * by advanced tools that update existing "Class" objects in-place
  195.      * and need to make the Introspector re-analyze an existing Class object.
  196.      *
  197.      * Note that only the direct state associated with the target Class
  198.      * object is flushed.  We do not flush state for other Class objects
  199.      * with the same name, nor do we flush state for any related Class
  200.      * objects (such as subclasses), even though their state may include
  201.      * information indirectly obtained from the target Class object.
  202.      *
  203.      * @param clz  Class object to be flushed.
  204.      */
  205.  
  206.     public static void flushFromCaches(Class clz) {
  207.     beanInfoCache.remove(clz);
  208.     declaredMethodCache.remove(clz);
  209.     }
  210.  
  211.     //======================================================================
  212.     //             Private implementation methods
  213.     //======================================================================
  214.  
  215.     private Introspector(Class beanClass, Class stopClass, int flags)
  216.                         throws IntrospectionException {
  217.     this.beanClass = beanClass;
  218.  
  219.     // Check stopClass is a superClass of startClass.
  220.     if (stopClass != null) {
  221.         boolean isSuper = false;
  222.         for (Class c = beanClass.getSuperclass(); c != null; c = c.getSuperclass()) {
  223.             if (c == stopClass) {
  224.             isSuper = true;
  225.             }
  226.         }
  227.         if (!isSuper) {
  228.             throw new IntrospectionException(stopClass.getName() + " not superclass of " + 
  229.                     beanClass.getName());
  230.         }
  231.     }
  232.  
  233.         if (flags == USE_ALL_BEANINFO) {
  234.         informant = findInformant(beanClass);
  235.         }
  236.  
  237.     Class superClass = beanClass.getSuperclass();
  238.     if (superClass != stopClass) {
  239.         int newFlags = flags;
  240.         if (newFlags == IGNORE_IMMEDIATE_BEANINFO) {
  241.         newFlags = USE_ALL_BEANINFO;
  242.         }
  243.         if (stopClass == null && newFlags == USE_ALL_BEANINFO) {
  244.         // We avoid going through getBeanInfo as we don't need
  245.         // it do copy the BeanInfo.
  246.         superBeanInfo = (BeanInfo)beanInfoCache.get(superClass);
  247.         if (superBeanInfo == null) {
  248.             Introspector ins = new Introspector(superClass, null, USE_ALL_BEANINFO);
  249.                 superBeanInfo = ins.getBeanInfo();
  250.                 beanInfoCache.put(superClass, superBeanInfo);
  251.         }
  252.         } else {
  253.         Introspector ins = new Introspector(superClass, stopClass, newFlags);
  254.             superBeanInfo = ins.getBeanInfo();
  255.         }
  256.     }
  257.     if (informant != null) {
  258.         additionalBeanInfo = informant.getAdditionalBeanInfo();
  259.     } 
  260.     if (additionalBeanInfo == null) {
  261.         additionalBeanInfo = new BeanInfo[0];
  262.     }
  263.     }
  264.  
  265.    
  266.     private GenericBeanInfo getBeanInfo() throws IntrospectionException {
  267.  
  268.     // the evaluation order here is import, as we evaluate the
  269.     // event sets and locate PropertyChangeListeners before we
  270.     // look for properties.
  271.     BeanDescriptor bd = getTargetBeanDescriptor();
  272.     EventSetDescriptor esds[] = getTargetEventInfo();
  273.     int defaultEvent = getTargetDefaultEventIndex();
  274.     PropertyDescriptor pds[] = getTargetPropertyInfo();
  275.     int defaultProperty = getTargetDefaultPropertyIndex();
  276.     MethodDescriptor mds[] = getTargetMethodInfo();
  277.  
  278.         return new GenericBeanInfo(bd, esds, defaultEvent, pds,
  279.             defaultProperty, mds, informant);
  280.     
  281.     }
  282.  
  283.     private BeanInfo findInformant(Class beanClass) {
  284.     String name = beanClass.getName() + "BeanInfo";
  285.         try {
  286.         return (java.beans.BeanInfo)instantiate(beanClass, name);
  287.     } catch (Exception ex) {
  288.         // Just drop through
  289.         }
  290.     // Now try checking if the bean is its own BeanInfo.
  291.         try {
  292.         if (isSubclass(beanClass, java.beans.BeanInfo.class)) {
  293.             return (java.beans.BeanInfo)beanClass.newInstance();
  294.         }
  295.     } catch (Exception ex) {
  296.         // Just drop through
  297.         }
  298.     // Now try looking for <searchPath>.fooBeanInfo
  299.        while (name.indexOf('.') > 0) {
  300.         name = name.substring(name.indexOf('.')+1);
  301.     }
  302.     for (int i = 0; i < searchPath.length; i++) {
  303.         try {
  304.         String fullName = searchPath[i] + "." + name;
  305.             return (java.beans.BeanInfo)instantiate(beanClass, fullName);
  306.         } catch (Exception ex) {
  307.            // Silently ignore any errors.
  308.         }
  309.     }
  310.     return null;
  311.     }
  312.  
  313.     /**
  314.      * @return An array of PropertyDescriptors describing the editable
  315.      * properties supported by the target bean.
  316.      */
  317.  
  318.     private PropertyDescriptor[] getTargetPropertyInfo() throws IntrospectionException {
  319.  
  320.     // Check if the bean has its own BeanInfo that will provide
  321.     // explicit information.
  322.         PropertyDescriptor[] explicit = null;
  323.     if (informant != null) {
  324.         explicit = informant.getPropertyDescriptors();
  325.         int ix = informant.getDefaultPropertyIndex();
  326.         if (ix >= 0 && ix < explicit.length) {
  327.         defaultPropertyName = explicit[ix].getName();
  328.         }
  329.         }
  330.  
  331.     if (explicit == null && superBeanInfo != null) {
  332.         // We have no explicit BeanInfo properties.  Check with our parent.
  333.         PropertyDescriptor supers[] = superBeanInfo.getPropertyDescriptors();
  334.         for (int i = 0 ; i < supers.length; i++) {
  335.         addProperty(supers[i]);
  336.         }
  337.         int ix = superBeanInfo.getDefaultPropertyIndex();
  338.         if (ix >= 0 && ix < supers.length) {
  339.         defaultPropertyName = supers[ix].getName();
  340.         }
  341.     }
  342.  
  343.     for (int i = 0; i < additionalBeanInfo.length; i++) {
  344.         PropertyDescriptor additional[] = additionalBeanInfo[i].getPropertyDescriptors();
  345.         if (additional != null) {
  346.             for (int j = 0 ; j < additional.length; j++) {
  347.             addProperty(additional[j]);
  348.             }
  349.         }
  350.     }
  351.  
  352.     if (explicit != null) {
  353.         // Add the explicit informant data to our results.
  354.         for (int i = 0 ; i < explicit.length; i++) {
  355.         addProperty(explicit[i]);
  356.         }
  357.  
  358.     } else {
  359.  
  360.         // Apply some reflection to the current class.
  361.  
  362.         // First get an array of all the beans methods at this level
  363.         Method methodList[] = getDeclaredMethods(beanClass);
  364.  
  365.         // Now analyze each method.
  366.         for (int i = 0; i < methodList.length; i++) {
  367.             Method method = methodList[i];
  368.             // skip static and non-public methods.
  369.         int mods = method.getModifiers();
  370.         if (Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
  371.             continue;
  372.         }
  373.             String name = method.getName();
  374.             Class argTypes[] = method.getParameterTypes();
  375.             Class resultType = method.getReturnType();
  376.         int argCount = argTypes.length;
  377.         PropertyDescriptor pd = null;
  378.  
  379.         try {
  380.  
  381.                 if (argCount == 0) {
  382.                 if (name.startsWith("get")) {
  383.                     // Simple getter
  384.                         pd = new PropertyDescriptor(decapitalize(name.substring(3)),
  385.                         method, null);
  386.                     } else if (resultType == boolean.class && name.startsWith("is")) {
  387.                     // Boolean getter
  388.                         pd = new PropertyDescriptor(decapitalize(name.substring(2)),
  389.                         method, null);
  390.                 }
  391.                 } else if (argCount == 1) {
  392.                 if (argTypes[0] == int.class && name.startsWith("get")) {
  393.                     pd = new IndexedPropertyDescriptor(
  394.                     decapitalize(name.substring(3)),
  395.                     null, null,
  396.                     method,    null);
  397.                 } else if (resultType == void.class && name.startsWith("set")) {
  398.                     // Simple setter
  399.                         pd = new PropertyDescriptor(decapitalize(name.substring(3)),
  400.                         null, method);
  401.                     if (throwsException(method, PropertyVetoException.class)) {
  402.                     pd.setConstrained(true);
  403.                 }            
  404.                 }
  405.                 } else if (argCount == 2) {
  406.                 if (argTypes[0] == int.class && name.startsWith("set")) {
  407.                         pd = new IndexedPropertyDescriptor(
  408.                         decapitalize(name.substring(3)),
  409.                         null, null,
  410.                         null, method);
  411.                     if (throwsException(method, PropertyVetoException.class)) {
  412.                     pd.setConstrained(true);            
  413.                 }
  414.             }
  415.             }
  416.         } catch (IntrospectionException ex) {
  417.             // This happens if a PropertyDescriptor or IndexedPropertyDescriptor
  418.                 // constructor fins that the method violates details of the deisgn
  419.             // pattern, e.g. by having an empty name, or a getter returning
  420.             // void , or whatever.
  421.             pd = null;
  422.         }
  423.  
  424.         if (pd != null) {
  425.             // If this class or one of its base classes is a PropertyChange
  426.             // source, then we assume that any properties we discover are "bound".
  427.             if (propertyChangeSource) {
  428.             pd.setBound(true);
  429.             }
  430.             addProperty(pd);
  431.         }
  432.         }
  433.     }
  434.  
  435.     // Allocate and populate the result array.
  436.     PropertyDescriptor result[] = new PropertyDescriptor[properties.size()];
  437.     java.util.Enumeration elements = properties.elements();
  438.     for (int i = 0; i < result.length; i++) {
  439.         result[i] = (PropertyDescriptor)elements.nextElement();
  440.         if (defaultPropertyName != null
  441.              && defaultPropertyName.equals(result[i].getName())) {
  442.         defaultPropertyIndex = i;
  443.         }
  444.     }
  445.  
  446.     return result;
  447.     }
  448.  
  449.     void addProperty(PropertyDescriptor pd) {
  450.     String name = pd.getName();
  451.     PropertyDescriptor old = (PropertyDescriptor)properties.get(name);
  452.     if (old == null) {
  453.         properties.put(name, pd);
  454.         return;
  455.     }
  456.     // If the property type has changed, use the new descriptor.
  457.     Class opd = old.getPropertyType();
  458.     Class npd = pd.getPropertyType();
  459.     if (opd != null && npd != null && opd != npd) {
  460.         properties.put(name, pd);
  461.         return;
  462.     }
  463.  
  464.     PropertyDescriptor composite;
  465.     if (old instanceof IndexedPropertyDescriptor ||
  466.                 pd instanceof IndexedPropertyDescriptor) {
  467.         composite = new IndexedPropertyDescriptor(old, pd);
  468.     } else {
  469.         composite = new PropertyDescriptor(old, pd);
  470.     }
  471.     properties.put(name, composite);
  472.     }
  473.  
  474.  
  475.     /**
  476.      * @return An array of EventSetDescriptors describing the kinds of 
  477.      * events fired by the target bean.
  478.      */
  479.     private EventSetDescriptor[] getTargetEventInfo() throws IntrospectionException {
  480.  
  481.     // Check if the bean has its own BeanInfo that will provide
  482.     // explicit information.
  483.         EventSetDescriptor[] explicit = null;
  484.     if (informant != null) {
  485.         explicit = informant.getEventSetDescriptors();
  486.         int ix = informant.getDefaultEventIndex();
  487.         if (ix >= 0 && ix < explicit.length) {
  488.         defaultEventName = explicit[ix].getName();
  489.         }
  490.     }
  491.  
  492.     if (explicit == null && superBeanInfo != null) {
  493.         // We have no explicit BeanInfo events.  Check with our parent.
  494.         EventSetDescriptor supers[] = superBeanInfo.getEventSetDescriptors();
  495.         for (int i = 0 ; i < supers.length; i++) {
  496.         addEvent(supers[i]);
  497.         }
  498.         int ix = superBeanInfo.getDefaultEventIndex();
  499.         if (ix >= 0 && ix < supers.length) {
  500.         defaultEventName = supers[ix].getName();
  501.         }
  502.     }
  503.  
  504.     for (int i = 0; i < additionalBeanInfo.length; i++) {
  505.         EventSetDescriptor additional[] = additionalBeanInfo[i].getEventSetDescriptors();
  506.         if (additional != null) {
  507.             for (int j = 0 ; j < additional.length; j++) {
  508.             addEvent(additional[j]);
  509.             }
  510.         }
  511.     }
  512.  
  513.     if (explicit != null) {
  514.         // Add the explicit informant data to our results.
  515.         for (int i = 0 ; i < explicit.length; i++) {
  516.         addEvent(explicit[i]);
  517.         }
  518.  
  519.     } else {
  520.  
  521.         // Apply some reflection to the current class.
  522.  
  523.         // Get an array of all the beans methods at this level
  524.         Method methodList[] = getDeclaredMethods(beanClass);
  525.  
  526.         // Find all suitable "add" and "remove" methods.
  527.         java.util.Hashtable adds = new java.util.Hashtable();
  528.         java.util.Hashtable removes = new java.util.Hashtable();
  529.         for (int i = 0; i < methodList.length; i++) {
  530.             Method method = methodList[i];
  531.             // skip static and non-public methods.
  532.         int mods = method.getModifiers();
  533.         if (Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
  534.             continue;
  535.         }
  536.             String name = method.getName();
  537.  
  538.             Class argTypes[] = method.getParameterTypes();
  539.             Class resultType = method.getReturnType();
  540.  
  541.             if (name.startsWith("add") && argTypes.length == 1 &&
  542.                     resultType == Void.TYPE) {
  543.             String compound = name.substring(3) + ":" + argTypes[0];
  544.             adds.put(compound, method);
  545.             } else if (name.startsWith("remove") && argTypes.length == 1 &&
  546.                     resultType == Void.TYPE) {
  547.             String compound = name.substring(6) + ":" + argTypes[0];
  548.             removes.put(compound, method);
  549.             }
  550.         }
  551.  
  552.            // Now look for matching addFooListener+removeFooListener pairs.
  553.           java.util.Enumeration keys = adds.keys();
  554.         String beanClassName = beanClass.getName();
  555.         while (keys.hasMoreElements()) {
  556.             String compound = (String) keys.nextElement();
  557.             // Skip any "add" which doesn't have a matching "remove".
  558.             if (removes.get(compound) == null) {
  559.             continue;
  560.             } 
  561.             // Method name has to end in "Listener"
  562.             if (compound.indexOf("Listener:") <= 0) {
  563.             continue;
  564.             }
  565.  
  566.             String listenerName = compound.substring(0, compound.indexOf(':'));
  567.             String eventName = decapitalize(listenerName.substring(0, listenerName.length()-8));
  568.             Method addMethod = (Method)adds.get(compound);
  569.             Method removeMethod = (Method)removes.get(compound);
  570.             Class argType = addMethod.getParameterTypes()[0];
  571.  
  572.             // Check if the argument type is a subtype of EventListener
  573.             if (!Introspector.isSubclass(argType, eventListenerType)) {
  574.                 continue;
  575.             }
  576.  
  577.                 // generate a list of Method objects for each of the target methods:
  578.             Method allMethods[] = argType.getMethods();
  579.             int count = 0;
  580.             for (int i = 0; i < allMethods.length; i++) {
  581.                 if (isEventHandler(allMethods[i])) {
  582.                 count++;
  583.                 } else {
  584.                 allMethods[i] = null;
  585.                 }
  586.             }
  587.             Method methods[] = new Method[count];
  588.             int j = 0;
  589.             for (int i = 0; i < allMethods.length; i++) {
  590.                 if (allMethods[i] != null) {
  591.                 methods[j++] = allMethods[i];
  592.                 }
  593.              }
  594.  
  595.               EventSetDescriptor esd = new EventSetDescriptor(eventName, argType,
  596.                         methods, addMethod, removeMethod);
  597.  
  598.         // If the adder method throws the TooManyListenersException then it
  599.         // is a Unicast event source.
  600.         if (throwsException(addMethod,
  601.             java.util.TooManyListenersException.class)) {
  602.             esd.setUnicast(true);
  603.         }
  604.  
  605.         addEvent(esd);
  606.         }
  607.     }
  608.  
  609.     // Allocate and populate the result array.
  610.     EventSetDescriptor result[] = new EventSetDescriptor[events.size()];
  611.     java.util.Enumeration elements = events.elements();
  612.     for (int i = 0; i < result.length; i++) {
  613.         result[i] = (EventSetDescriptor)elements.nextElement();
  614.         if (defaultEventName != null 
  615.                 && defaultEventName.equals(result[i].getName())) {
  616.         defaultEventIndex = i;
  617.         }
  618.     }
  619.  
  620.     return result;
  621.     }
  622.  
  623.     void addEvent(EventSetDescriptor esd) {
  624.     String key = esd.getName() + esd.getListenerType();
  625.     if (esd.getName().equals("propertyChange")) {
  626.         propertyChangeSource = true;
  627.     }
  628.     EventSetDescriptor old = (EventSetDescriptor)events.get(key);
  629.     if (old == null) {
  630.         events.put(key, esd);
  631.         return;
  632.     }
  633.     EventSetDescriptor composite = new EventSetDescriptor(old, esd);
  634.     events.put(key, composite);
  635.     }
  636.  
  637.     /**
  638.      * @return An array of MethodDescriptors describing the private
  639.      * methods supported by the target bean.
  640.      */
  641.     private MethodDescriptor[] getTargetMethodInfo() throws IntrospectionException {
  642.  
  643.     // Check if the bean has its own BeanInfo that will provide
  644.     // explicit information.
  645.         MethodDescriptor[] explicit = null;
  646.     if (informant != null) {
  647.         explicit = informant.getMethodDescriptors();
  648.     }
  649.  
  650.     if (explicit == null && superBeanInfo != null) {
  651.         // We have no explicit BeanInfo methods.  Check with our parent.
  652.         MethodDescriptor supers[] = superBeanInfo.getMethodDescriptors();
  653.         for (int i = 0 ; i < supers.length; i++) {
  654.         addMethod(supers[i]);
  655.         }
  656.     }
  657.  
  658.     for (int i = 0; i < additionalBeanInfo.length; i++) {
  659.         MethodDescriptor additional[] = additionalBeanInfo[i].getMethodDescriptors();
  660.         if (additional != null) {
  661.             for (int j = 0 ; j < additional.length; j++) {
  662.             addMethod(additional[j]);
  663.             }
  664.         }
  665.     }
  666.  
  667.     if (explicit != null) {
  668.         // Add the explicit informant data to our results.
  669.         for (int i = 0 ; i < explicit.length; i++) {
  670.         addMethod(explicit[i]);
  671.         }
  672.  
  673.     } else {
  674.  
  675.         // Apply some reflection to the current class.
  676.  
  677.         // First get an array of all the beans methods at this level
  678.         Method methodList[] = getDeclaredMethods(beanClass);
  679.  
  680.         // Now analyze each method.
  681.         for (int i = 0; i < methodList.length; i++) {
  682.             Method method = methodList[i];
  683.             // skip non-public methods.
  684.         if (!Modifier.isPublic(method.getModifiers())) {
  685.             continue;
  686.         }
  687.         MethodDescriptor md = new MethodDescriptor(method);
  688.         addMethod(md);
  689.         }
  690.     }
  691.  
  692.     // Allocate and populate the result array.
  693.     MethodDescriptor result[] = new MethodDescriptor[methods.size()];
  694.     java.util.Enumeration elements = methods.elements();
  695.     for (int i = 0; i < result.length; i++) {
  696.         result[i] = (MethodDescriptor)elements.nextElement();
  697.     }
  698.  
  699.     return result;
  700.     }
  701.  
  702.     private void addMethod(MethodDescriptor md) {
  703.     // We have to be careful here to distinguish method by both name
  704.     // and argument lists.
  705.     // This method gets called a *lot, so we try to be efficient.
  706.  
  707.     String name = md.getMethod().getName();
  708.  
  709.     MethodDescriptor old = (MethodDescriptor)methods.get(name);
  710.     if (old == null) {
  711.         // This is the common case.
  712.         methods.put(name, md);
  713.         return;
  714.     }    
  715.  
  716.     // We have a collision on method names.  This is rare.
  717.  
  718.     // Check if old and md have the same type.
  719.     Class p1[] = md.getMethod().getParameterTypes();    
  720.     Class p2[] = old.getMethod().getParameterTypes();    
  721.     boolean match = false;
  722.     if (p1.length == p2.length) {
  723.         match = true;
  724.         for (int i = 0; i < p1.length; i++) {
  725.         if (p1[i] != p2[i]) {
  726.             match = false;
  727.             break;
  728.         }
  729.         }
  730.     }
  731.     if (match) {
  732.         MethodDescriptor composite = new MethodDescriptor(old, md);
  733.         methods.put(name, composite);
  734.         return;
  735.     }
  736.  
  737.     // We have a collision on method names with different type signatures.
  738.     // This is very rare.
  739.  
  740.     String longKey = makeQualifiedMethodName(md);
  741.     old = (MethodDescriptor)methods.get(longKey);
  742.     if (old == null) {
  743.         methods.put(longKey, md);
  744.         return;
  745.     }    
  746.     MethodDescriptor composite = new MethodDescriptor(old, md);
  747.     methods.put(longKey, composite);
  748.     }
  749.  
  750.     private String makeQualifiedMethodName(MethodDescriptor md) {
  751.     Method m = md.getMethod();
  752.     StringBuffer sb = new StringBuffer();
  753.     sb.append(m.getName());
  754.     sb.append("=");
  755.     Class params[] = m.getParameterTypes();
  756.     for (int i = 0; i < params.length; i++) {
  757.         sb.append(":");
  758.         sb.append(params[i].getName());
  759.     }
  760.     return sb.toString();
  761.     }
  762.  
  763.     private int getTargetDefaultEventIndex() {
  764.     return defaultEventIndex;
  765.     }
  766.  
  767.     private int getTargetDefaultPropertyIndex() {
  768.     return defaultPropertyIndex;
  769.     }
  770.  
  771.     private BeanDescriptor getTargetBeanDescriptor() throws IntrospectionException {
  772.     // Use explicit info, if available,
  773.     if (informant != null) {
  774.         BeanDescriptor bd = informant.getBeanDescriptor();
  775.         if (bd != null) {
  776.         return (bd);
  777.         }
  778.     }
  779.     // OK, fabricate a default BeanDescriptor.
  780.     return (new BeanDescriptor(beanClass));
  781.     }
  782.  
  783.     private boolean isEventHandler(Method m) throws IntrospectionException {
  784.     // We assume that a method is an event handler if it has a single
  785.         // argument, whose type inherit from java.util.Event.
  786.  
  787.     try {
  788.         Class argTypes[] = m.getParameterTypes();
  789.         if (argTypes.length != 1) {
  790.         return false;
  791.         }
  792.         if (isSubclass(argTypes[0], java.util.EventObject.class)) {
  793.         return true;
  794.         } else {
  795.         return false;
  796.         }
  797.     } catch (Exception ex) {
  798.         throw new IntrospectionException("Unexpected reflection exception: " + ex);
  799.     }
  800.     }
  801.  
  802.     private static synchronized Method[] getDeclaredMethods(Class clz) {
  803.     // Looking up Class.getDeclaredMethods is realtively expensive,
  804.     // so we cache the results.
  805.     Method[] result = (Method[])declaredMethodCache.get(clz);
  806.     if (result == null) {
  807.         result = clz.getDeclaredMethods();
  808.         declaredMethodCache.put(clz, result);
  809.     }
  810.     return result;
  811.     }
  812.  
  813.     //======================================================================
  814.     // Package private support methods.
  815.     //======================================================================
  816.  
  817.     /**
  818.      * Internal support for finding a target methodName on a given class.
  819.      */
  820.     private static Method internalFindMethod(Class start, String methodName,
  821.                                  int argCount) {
  822.  
  823.     // For overriden methods we need to find the most derived version.
  824.     // So we start with the given class and walk up the superclass chain.
  825.     for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
  826.             Method methods[] = getDeclaredMethods(cl);
  827.         for (int i = 0; i < methods.length; i++) {
  828.             Method method = methods[i];
  829.             // skip static and non-public methods.
  830.         int mods = method.getModifiers();
  831.         if (Modifier.isStatic(mods) || !Modifier.isPublic(mods)) {
  832.             continue;
  833.         }
  834.             if (method.getName().equals(methodName) &&
  835.             method.getParameterTypes().length == argCount) {
  836.                 return method;
  837.              }
  838.         }
  839.     }
  840.  
  841.     // Now check any inherited interfaces.  This is necessary both when
  842.     // the argument class is itself an interface, and when the argument
  843.     // class is an abstract class.
  844.     Class ifcs[] = start.getInterfaces();
  845.     for (int i = 0 ; i < ifcs.length; i++) {
  846.         Method m = internalFindMethod(ifcs[i], methodName, argCount);
  847.         if (m != null) {
  848.         return m;
  849.         }
  850.     }
  851.  
  852.     return null;
  853.     }
  854.  
  855.     /**
  856.      * Find a target methodName on a given class.
  857.      */
  858.     static Method findMethod(Class cls, String methodName, int argCount) 
  859.             throws IntrospectionException {
  860.     if (methodName == null) {
  861.         return null;
  862.     }
  863.  
  864.     Method m = internalFindMethod(cls, methodName, argCount);
  865.     if (m != null ) {
  866.         return m;
  867.     }
  868.  
  869.     // We failed to find a suitable method
  870.     throw new IntrospectionException("No method \"" + methodName + 
  871.                     "\" with " + argCount + " arg(s)");
  872.     }
  873.  
  874.     /**
  875.      * Return true if class a is either equivalent to class b, or
  876.      * if class a is a subclass of class b, i.e. if a either "extends"
  877.      * or "implements" b.
  878.      * Note tht either or both "Class" objects may represent interfaces.
  879.      */
  880.     static  boolean isSubclass(Class a, Class b) {
  881.     // We rely on the fact that for any given java class or
  882.         // primtitive type there is a unqiue Class object, so
  883.     // we can use object equivalence in the comparisons.
  884.     if (a == b) {
  885.         return true;
  886.     }
  887.     if (a == null || b == null) {
  888.         return false;
  889.     }
  890.     for (Class x = a; x != null; x = x.getSuperclass()) {
  891.         if (x == b) {    
  892.         return true;
  893.         }
  894.         if (b.isInterface()) {
  895.         Class interfaces[] = x.getInterfaces();
  896.         for (int i = 0; i < interfaces.length; i++) {
  897.             if (isSubclass(interfaces[i], b)) {
  898.             return true;
  899.             }
  900.         }
  901.         }
  902.     }
  903.     return false;
  904.     }
  905.  
  906.     /**
  907.      * Return true iff the given method throws the given exception.
  908.      */
  909.     private boolean throwsException(Method method, Class exception) {
  910.     Class exs[] = method.getExceptionTypes();
  911.     for (int i = 0; i < exs.length; i++) {
  912.         if (exs[i] == exception) {
  913.         return true;
  914.         }
  915.     }
  916.     return false;
  917.     }
  918.  
  919.     /**
  920.      * Try to create an instance of a named class.
  921.      * First try the classloader of "sibling", then try the system
  922.      * classloader.
  923.      */
  924.  
  925.     static Object instantiate(Class sibling, String className)
  926.          throws InstantiationException, IllegalAccessException,
  927.                         ClassNotFoundException {
  928.     // First check with sibling's classloader (if any). 
  929.     ClassLoader cl = sibling.getClassLoader();
  930.     if (cl != null) {
  931.         try {
  932.             Class cls = cl.loadClass(className);
  933.         return cls.newInstance();
  934.         } catch (Exception ex) {
  935.             // Just drop through and try the system classloader.
  936.         }
  937.         }
  938.     // Now try the system classloader.
  939.     Class cls = Class.forName(className);
  940.     return cls.newInstance();
  941.     }
  942.  
  943.     //======================================================================
  944.  
  945.     private BeanInfo informant;
  946.     private boolean propertyChangeSource = false;
  947.     private Class beanClass;
  948.     private BeanInfo superBeanInfo;
  949.     private BeanInfo additionalBeanInfo[];
  950.     private static java.util.Hashtable beanInfoCache = new java.util.Hashtable();
  951.     private static Class eventListenerType = java.util.EventListener.class;
  952.     private String defaultEventName;
  953.     private String defaultPropertyName;
  954.     private int defaultEventIndex = -1;
  955.     private int defaultPropertyIndex = -1;
  956.  
  957.     // Methods maps from Method objects to MethodDescriptors
  958.     private java.util.Hashtable methods = new java.util.Hashtable();
  959.  
  960.     // Cache of Class.getDeclaredMethods:
  961.     private static java.util.Hashtable declaredMethodCache = new java.util.Hashtable();
  962.  
  963.     // properties maps from String names to PropertyDescriptors
  964.     private java.util.Hashtable properties = new java.util.Hashtable();
  965.  
  966.     // events maps from String names to EventSetDescriptors
  967.     private java.util.Hashtable events = new java.util.Hashtable();
  968.  
  969.     private static String[] searchPath = { "sun.beans.infos" };
  970.  
  971. }
  972.  
  973. //===========================================================================
  974.  
  975. /**
  976.  * Package private implementation support class for Introspector's
  977.  * internal use.
  978.  */
  979.  
  980. class GenericBeanInfo extends SimpleBeanInfo {
  981.  
  982.     public GenericBeanInfo(BeanDescriptor beanDescriptor,
  983.         EventSetDescriptor[] events, int defaultEvent,
  984.         PropertyDescriptor[] properties, int defaultProperty,
  985.         MethodDescriptor[] methods, BeanInfo targetBeanInfo) {
  986.     this.beanDescriptor = beanDescriptor;
  987.     this.events = events;
  988.     this.defaultEvent = defaultEvent;
  989.     this.properties = properties;
  990.     this.defaultProperty = defaultProperty;
  991.     this.methods = methods;
  992.     this.targetBeanInfo = targetBeanInfo;
  993.     }
  994.  
  995.     /**
  996.      * Package-private dup constructor
  997.      * This must isolate the new object from any changes to the old object.
  998.      */
  999.     GenericBeanInfo(GenericBeanInfo old) {
  1000.  
  1001.     beanDescriptor = new BeanDescriptor(old.beanDescriptor);
  1002.     if (old.events != null) {
  1003.         int len = old.events.length;
  1004.         events = new EventSetDescriptor[len];
  1005.         for (int i = 0; i < len; i++) {
  1006.         events[i] = new EventSetDescriptor(old.events[i]);
  1007.         }
  1008.     }
  1009.     defaultEvent = old.defaultEvent;
  1010.     if (old.properties != null) {
  1011.         int len = old.properties.length;
  1012.         properties = new PropertyDescriptor[len];
  1013.         for (int i = 0; i < len; i++) {
  1014.         PropertyDescriptor oldp = old.properties[i];
  1015.         if (oldp instanceof IndexedPropertyDescriptor) {
  1016.             properties[i] = new IndexedPropertyDescriptor(
  1017.                     (IndexedPropertyDescriptor) oldp);
  1018.         } else {
  1019.             properties[i] = new PropertyDescriptor(oldp);
  1020.         }
  1021.         }
  1022.     }
  1023.     defaultProperty = old.defaultProperty;
  1024.     if (old.methods != null) {
  1025.         int len = old.methods.length;
  1026.         methods = new MethodDescriptor[len];
  1027.         for (int i = 0; i < len; i++) {
  1028.         methods[i] = new MethodDescriptor(old.methods[i]);
  1029.         }
  1030.     }
  1031.     targetBeanInfo = old.targetBeanInfo;
  1032.     }
  1033.  
  1034.     public PropertyDescriptor[] getPropertyDescriptors() {
  1035.     return properties;
  1036.     }
  1037.  
  1038.     public int getDefaultPropertyIndex() {
  1039.     return defaultProperty;
  1040.     }
  1041.  
  1042.     public EventSetDescriptor[] getEventSetDescriptors() {
  1043.     return events;
  1044.     }
  1045.  
  1046.     public int getDefaultEventIndex() {
  1047.     return defaultEvent;
  1048.     }
  1049.  
  1050.     public MethodDescriptor[] getMethodDescriptors() {
  1051.     return methods;
  1052.     }
  1053.  
  1054.     public BeanDescriptor getBeanDescriptor() {
  1055.     return beanDescriptor;
  1056.     }
  1057.  
  1058.     public java.awt.Image getIcon(int iconKind) {
  1059.     if (targetBeanInfo != null) {
  1060.         return targetBeanInfo.getIcon(iconKind);
  1061.     }
  1062.     return super.getIcon(iconKind);
  1063.     }
  1064.  
  1065.     private BeanDescriptor beanDescriptor;
  1066.     private EventSetDescriptor[] events;
  1067.     private int defaultEvent;
  1068.     private PropertyDescriptor[] properties;
  1069.     private int defaultProperty;
  1070.     private MethodDescriptor[] methods;
  1071.     private BeanInfo targetBeanInfo;
  1072. }
  1073.