001/* java.beans.Introspector
002   Copyright (C) 1998, 2002, 2003 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010 
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.beans;
040
041import gnu.java.beans.BeanInfoEmbryo;
042import gnu.java.beans.ExplicitBeanInfo;
043import gnu.java.beans.IntrospectionIncubator;
044import gnu.java.lang.ClassHelper;
045
046import java.util.Hashtable;
047import java.util.Vector;
048
049/**
050 * Introspector is the class that does the bulk of the
051 * design-time work in Java Beans.  Every class must have
052 * a BeanInfo in order for an RAD tool to use it; but, as
053 * promised, you don't have to write the BeanInfo class
054 * yourself if you don't want to.  All you have to do is
055 * call getBeanInfo() in the Introspector and it will use
056 * standard JavaBeans-defined method signatures to
057 * determine the information about your class.<P>
058 *
059 * Don't worry about it too much, though: you can provide
060 * JavaBeans with as much customized information as you
061 * want, or as little as you want, using the BeanInfo
062 * interface (see BeanInfo for details).<P>
063 *
064 * <STRONG>Order of Operations</STRONG><P>
065 *
066 * When you call getBeanInfo(class c), the Introspector
067 * first searches for BeanInfo class to see if you
068 * provided any explicit information.  It searches for a
069 * class named &lt;bean class name&gt;BeanInfo in different
070 * packages, first searching the bean class's package
071 * and then moving on to search the beanInfoSearchPath.<P>
072 *
073 * If it does not find a BeanInfo class, it acts as though
074 * it had found a BeanInfo class returning null from all
075 * methods (meaning it should discover everything through
076 * Introspection).  If it does, then it takes the
077 * information it finds in the BeanInfo class to be
078 * canonical (that is, the information speaks for its
079 * class as well as all superclasses).<P>
080 *
081 * When it has introspected the class, calls
082 * getBeanInfo(c.getSuperclass) and adds that information
083 * to the information it has, not adding to any information
084 * it already has that is canonical.<P>
085 *
086 * <STRONG>Introspection Design Patterns</STRONG><P>
087 *
088 * When the Introspector goes in to read the class, it
089 * follows a well-defined order in order to not leave any
090 * methods unaccounted for.  Its job is to step over all
091 * of the public methods in a class and determine whether
092 * they are part of a property, an event, or a method (in
093 * that order).
094 *
095 *
096 * <STRONG>Properties:</STRONG><P>
097 * 
098 * <OL>
099 * <LI>If there is a <CODE>public boolean isXXX()</CODE>
100 *     method, then XXX is a read-only boolean property.
101 *     <CODE>boolean getXXX()</CODE> may be supplied in
102 *     addition to this method, although isXXX() is the
103 *     one that will be used in this case and getXXX()
104 *     will be ignored.  If there is a
105 *     <CODE>public void setXXX(boolean)</CODE> method,
106 *     it is part of this group and makes it a read-write
107 *     property.</LI>
108 * <LI>If there is a
109 *     <CODE>public &lt;type&gt; getXXX(int)</CODE>
110 *     method, then XXX is a read-only indexed property of
111 *     type &lt;type&gt;.  If there is a
112 *     <CODE>public void setXXX(int,&lt;type&gt;)</CODE>
113 *     method, then it is a read-write indexed property of
114 *     type &lt;type&gt;.  There may also be a
115 *     <CODE>public &lt;type&gt;[] getXXX()</CODE> and a
116 *     <CODE>public void setXXX(&lt;type&gt;)</CODE>
117 *     method as well.</LI>
118 * <LI>If there is a
119 *     <CODE>public void setXXX(int,&lt;type&gt;)</CODE>
120 *     method, then it is a write-only indexed property of
121 *     type &lt;type&gt;.  There may also be a
122 *     <CODE>public &lt;type&gt;[] getXXX()</CODE> and a
123 *     <CODE>public void setXXX(&lt;type&gt;)</CODE>
124 *     method as well.</LI>
125 * <LI>If there is a
126 *     <CODE>public &lt;type&gt; getXXX()</CODE> method,
127 *     then XXX is a read-only property of type
128 *     &lt;type&gt;.  If there is a
129 *     <CODE>public void setXXX(&lt;type&gt;)</CODE>
130 *     method, then it will be used for the property and
131 *     the property will be considered read-write.</LI>
132 * <LI>If there is a
133 *     <CODE>public void setXXX(&lt;type&gt;)</CODE>
134 *     method, then as long as XXX is not already used as
135 *     the name of a property, XXX is assumed to be a
136 *     write-only property of type &lt;type&gt;.</LI>
137 * <LI>In all of the above cases, if the setXXX() method
138 *     throws <CODE>PropertyVetoException</CODE>, then the
139 *     property in question is assumed to be constrained.
140 *     No properties are ever assumed to be bound
141 *     (<STRONG>Spec Note:</STRONG> this is not in the
142 *     spec, it just makes sense).  See PropertyDescriptor
143 *     for a description of bound and constrained
144 *     properties.</LI>
145 * </OL>
146 *
147 * <STRONG>Events:</STRONG><P>
148 *
149 * If there is a pair of methods,
150 * <CODE>public void addXXX(&lt;type&gt;)</CODE> and
151 * <CODE>public void removeXXX(&lt;type&gt;)</CODE>, where
152 * &lt;type&gt; is a descendant of
153 * <CODE>java.util.EventListener</CODE>, then the pair of
154 * methods imply that this Bean will fire events to
155 * listeners of type &lt;type&gt;.<P>
156 *
157 * If the addXXX() method throws
158 * <CODE>java.util.TooManyListenersException</CODE>, then
159 * the event set is assumed to be <EM>unicast</EM>.  See
160 * EventSetDescriptor for a discussion of unicast event
161 * sets.<P>
162 *
163 * <STRONG>Spec Note:</STRONG> the spec seems to say that
164 * the listener type's classname must be equal to the XXX
165 * part of addXXX() and removeXXX(), but that is not the
166 * case in Sun's implementation, so I am assuming it is
167 * not the case in general.<P>
168 *
169 * <STRONG>Methods:</STRONG><P>
170 * 
171 * Any public methods (including those which were used
172 * for Properties or Events) are used as Methods.
173 *
174 * @author John Keiser
175 * @since JDK1.1
176 * @see java.beans.BeanInfo
177 */
178public class Introspector {
179  
180  public static final int USE_ALL_BEANINFO = 1;
181  public static final int IGNORE_IMMEDIATE_BEANINFO = 2;
182  public static final int IGNORE_ALL_BEANINFO = 3;
183
184  static String[] beanInfoSearchPath = {"gnu.java.beans.info"};
185  static Hashtable<Class<?>,BeanInfo> beanInfoCache = 
186    new Hashtable<Class<?>,BeanInfo>();
187  
188  private Introspector() {}
189  
190  /** 
191   * Get the BeanInfo for class <CODE>beanClass</CODE>,
192   * first by looking for explicit information, next by
193   * using standard design patterns to determine
194   * information about the class.
195   *
196   * @param beanClass the class to get BeanInfo about.
197   * @return the BeanInfo object representing the class.
198   */
199  public static BeanInfo getBeanInfo(Class<?> beanClass) 
200    throws IntrospectionException 
201  {
202    BeanInfo cachedInfo;
203    synchronized(beanClass) 
204      {
205        cachedInfo = beanInfoCache.get(beanClass);
206        if(cachedInfo != null) 
207          {
208            return cachedInfo;
209          }
210        cachedInfo = getBeanInfo(beanClass,null);
211        beanInfoCache.put(beanClass,cachedInfo);
212        return cachedInfo;
213      }
214  }
215  
216  /**
217   * Returns a {@BeanInfo} instance for the given Bean class where a flag
218   * controls the usage of explicit BeanInfo class to retrieve that
219   * information.
220   * 
221   * <p>You have three options:</p>
222   * <p>With {@link #USE_ALL_BEANINFO} the result is the same as
223   * {@link #getBeanInfo(Class)}.</p>
224   * 
225   * <p>Calling the method with <code>flag</code> set to
226   * {@link #IGNORE_IMMEDIATE_BEANINFO} will let it use all
227   * explicit BeanInfo classes for the beans superclasses
228   * but not for the bean class itself. Furthermore eventset,
229   * property and method information is retrieved by introspection
230   * if the explicit <code>BeanInfos</code> did not provide such data
231   * (ie. return <code>null</code> on {@link BeanInfo.getMethodDescriptors},
232   * {@link BeanInfo.getEventSetDescriptors} and
233   * {@link BeanInfo.getPropertyDescriptors}.)
234   * </p>
235   * 
236   * <p>When the method is called with <code>flag</code< set to
237   * {@link #IGNORE_ALL_BEANINFO} all the bean data is retrieved
238   * by inspecting the class.</p>
239   * 
240   * <p>Note: Any unknown value for <code>flag</code> is interpreted
241   * as {@link #IGNORE_ALL_BEANINFO}</p>.
242   * 
243   * @param beanClass The class whose BeanInfo should be returned.
244   * @param flag Controls the usage of explicit <code>BeanInfo</code> classes.
245   * @return A BeanInfo object describing the class. 
246   * @throws IntrospectionException If something goes wrong while retrieving
247   *    the bean data.
248   */
249  public static BeanInfo getBeanInfo(Class<?> beanClass, int flag)
250    throws IntrospectionException
251  {
252    IntrospectionIncubator ii;
253    BeanInfoEmbryo infoEmbryo;
254    
255    switch(flag)
256    {
257      case USE_ALL_BEANINFO:
258        return getBeanInfo(beanClass);
259      case IGNORE_IMMEDIATE_BEANINFO:
260        Class superclass = beanClass.getSuperclass();
261        ExplicitInfo explicit = new ExplicitInfo(superclass, null);
262        
263        ii = new IntrospectionIncubator();
264        if (explicit.explicitEventSetDescriptors != null)
265          ii.setEventStopClass(superclass);
266        
267        if (explicit.explicitMethodDescriptors != null)
268          ii.setMethodStopClass(superclass);
269        
270        if (explicit.explicitPropertyDescriptors != null)
271          ii.setPropertyStopClass(superclass);
272        
273        ii.addMethods(beanClass.getMethods());
274
275        infoEmbryo = ii.getBeanInfoEmbryo();
276        merge(infoEmbryo, explicit);
277
278        infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null));
279        
280        return infoEmbryo.getBeanInfo();
281      case IGNORE_ALL_BEANINFO:
282      default:
283        ii = new IntrospectionIncubator();
284        ii.addMethods(beanClass.getMethods());
285        infoEmbryo = ii.getBeanInfoEmbryo();
286        infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null));
287        
288        return infoEmbryo.getBeanInfo();
289    }
290  }
291
292  /**
293   * Flush all of the Introspector's internal caches.
294   *
295   * @since 1.2
296   */
297  public static void flushCaches()
298  {
299    beanInfoCache.clear();
300
301        // Clears all the intermediate ExplicitInfo instances which
302        // have been created.
303        // This makes sure we have to retrieve stuff like BeanDescriptors
304        // again. (Remember that FeatureDescriptor can be modified by the user.)
305        ExplicitInfo.flushCaches();
306  }
307
308  /**
309   * Flush the Introspector's internal cached information for a given
310   * class.
311   *
312   * @param clz the class to be flushed.
313   * @throws NullPointerException if clz is null.
314   * @since 1.2
315   */
316  public static void flushFromCaches(Class<?> clz)
317  {
318    synchronized (clz)
319      {
320        beanInfoCache.remove(clz);
321      }
322  }
323
324  /** Adds all explicity given bean info data to the introspected
325   * data.
326   * 
327   * @param infoEmbryo Bean info data retrieved by introspection.
328   * @param explicit Bean info data retrieved by BeanInfo classes.
329   */
330  private static void merge(BeanInfoEmbryo infoEmbryo, ExplicitInfo explicit)
331  {
332    PropertyDescriptor[] p = explicit.explicitPropertyDescriptors;
333    if(p!=null) 
334      {
335    for(int i=0;i<p.length;i++) 
336      {
337        if(!infoEmbryo.hasProperty(p[i])) 
338          {
339        infoEmbryo.addProperty(p[i]);
340          }
341      }
342    
343    // -1 should be used to denote a missing default property but
344    // for robustness reasons any value below zero is discarded.
345    // Not doing so would let Classpath fail where the JDK succeeds.
346    if(explicit.defaultProperty > -1) 
347      {
348        infoEmbryo.setDefaultPropertyName(p[explicit.defaultProperty].getName());
349      }
350      }
351    EventSetDescriptor[] e = explicit.explicitEventSetDescriptors;
352    if(e!=null) 
353      {
354    for(int i=0;i<e.length;i++) 
355      {
356        if(!infoEmbryo.hasEvent(e[i])) 
357          {
358        infoEmbryo.addEvent(e[i]);
359          }
360      }
361    
362    // -1 should be used to denote a missing default event but
363    // for robustness reasons any value below zero is discarded.
364    // Not doing so would let Classpath fail where the JDK succeeds.
365    if(explicit.defaultEvent > -1) 
366      {
367        infoEmbryo.setDefaultEventName(e[explicit.defaultEvent].getName());
368      }
369      }
370    MethodDescriptor[] m = explicit.explicitMethodDescriptors;
371    if(m!=null) 
372      {
373    for(int i=0;i<m.length;i++) 
374      {
375        if(!infoEmbryo.hasMethod(m[i])) 
376          {
377        infoEmbryo.addMethod(m[i]);
378          }
379      }
380      }
381
382    infoEmbryo.setAdditionalBeanInfo(explicit.explicitBeanInfo);
383    infoEmbryo.setIcons(explicit.im);
384    
385  }
386  
387  /** 
388   * Get the BeanInfo for class <CODE>beanClass</CODE>,
389   * first by looking for explicit information, next by
390   * using standard design patterns to determine
391   * information about the class.  It crawls up the
392   * inheritance tree until it hits <CODE>topClass</CODE>.
393   *
394   * @param beanClass the Bean class.
395   * @param stopClass the class to stop at.
396   * @return the BeanInfo object representing the class.
397   */
398  public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass) 
399    throws IntrospectionException 
400  {
401    ExplicitInfo explicit = new ExplicitInfo(beanClass, stopClass);
402
403    IntrospectionIncubator ii = new IntrospectionIncubator();
404    ii.setPropertyStopClass(explicit.propertyStopClass);
405    ii.setEventStopClass(explicit.eventStopClass);
406    ii.setMethodStopClass(explicit.methodStopClass);
407    ii.addMethods(beanClass.getMethods());
408    
409    BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo();
410    
411    merge(currentInfo, explicit);
412    
413    //  Sets the info's BeanDescriptor to the one we extracted from the
414    // explicit BeanInfo instance(s) if they contained one. Otherwise we
415    // create the BeanDescriptor from scratch.
416    // Note: We do not create a copy the retrieved BeanDescriptor which will allow
417    // the user to modify the instance while it is cached. However this is how
418    // the RI does it.
419    currentInfo.setBeanDescriptor(
420        (explicit.explicitBeanDescriptor == null ? 
421            new BeanDescriptor(beanClass, null) :
422            explicit.explicitBeanDescriptor));    
423    return currentInfo.getBeanInfo();
424  }
425  
426  /** 
427   * Get the search path for BeanInfo classes.
428   *
429   * @return the BeanInfo search path.
430   */
431  public static String[] getBeanInfoSearchPath() 
432  {
433    return beanInfoSearchPath;
434  }
435  
436  /** 
437   * Set the search path for BeanInfo classes.
438   * @param beanInfoSearchPath the new BeanInfo search
439   *        path.
440   */
441  public static void setBeanInfoSearchPath(String[] beanInfoSearchPath) 
442  {
443    Introspector.beanInfoSearchPath = beanInfoSearchPath;
444  }
445  
446  /** 
447   * A helper method to convert a name to standard Java
448   * naming conventions: anything with two capitals as the
449   * first two letters remains the same, otherwise the
450   * first letter is decapitalized.  URL = URL, I = i,
451   * MyMethod = myMethod.
452   *
453   * @param name the name to decapitalize.
454   * @return the decapitalized name.
455   */
456  public static String decapitalize(String name) 
457  {
458    try 
459      {
460      if(!Character.isUpperCase(name.charAt(0))) 
461        {
462          return name;
463        } 
464      else 
465        {
466        try 
467          {
468          if(Character.isUpperCase(name.charAt(1))) 
469            {
470              return name;
471            } 
472          else 
473            {
474              char[] c = name.toCharArray();
475              c[0] = Character.toLowerCase(c[0]);
476              return new String(c);
477            }
478          } 
479        catch(StringIndexOutOfBoundsException E) 
480          {
481            char[] c = new char[1];
482            c[0] = Character.toLowerCase(name.charAt(0));
483            return new String(c);
484          }
485        }
486      } 
487    catch(StringIndexOutOfBoundsException E) 
488      {
489        return name;
490      } 
491    catch(NullPointerException E) 
492      {
493        return null;
494      }
495  }
496
497  static BeanInfo copyBeanInfo(BeanInfo b) 
498  {
499    java.awt.Image[] icons = new java.awt.Image[4];
500    for(int i=1;i<=4;i++) 
501      {
502        icons[i-1] = b.getIcon(i);
503      }
504
505    return new ExplicitBeanInfo(b.getBeanDescriptor(),
506                                b.getAdditionalBeanInfo(),
507                                b.getPropertyDescriptors(),
508                                b.getDefaultPropertyIndex(),
509                                b.getEventSetDescriptors(),
510                                b.getDefaultEventIndex(),
511                                b.getMethodDescriptors(),
512                                icons);
513  }
514}
515
516class ExplicitInfo 
517{
518  BeanDescriptor explicitBeanDescriptor;
519  BeanInfo[] explicitBeanInfo;
520  
521  PropertyDescriptor[] explicitPropertyDescriptors;
522  EventSetDescriptor[] explicitEventSetDescriptors;
523  MethodDescriptor[] explicitMethodDescriptors;
524  
525  int defaultProperty;
526  int defaultEvent;
527  
528  java.awt.Image[] im = new java.awt.Image[4];
529  
530  Class propertyStopClass;
531  Class eventStopClass;
532  Class methodStopClass;
533
534  static Hashtable explicitBeanInfos = new Hashtable();
535  static Vector emptyBeanInfos = new Vector();
536
537  ExplicitInfo(Class beanClass, Class stopClass) 
538  {
539    while(beanClass != null && !beanClass.equals(stopClass)) 
540      {
541
542        BeanInfo explicit = findExplicitBeanInfo(beanClass);
543        
544
545        if(explicit != null) 
546          {
547
548            if(explicitBeanDescriptor == null) 
549              {
550                explicitBeanDescriptor = explicit.getBeanDescriptor();
551              }
552
553            if(explicitBeanInfo == null) 
554              {
555                explicitBeanInfo = explicit.getAdditionalBeanInfo();
556              }
557
558            if(explicitPropertyDescriptors == null) 
559              {
560                if(explicit.getPropertyDescriptors() != null) 
561                  {
562                    explicitPropertyDescriptors = explicit.getPropertyDescriptors();
563                    defaultProperty = explicit.getDefaultPropertyIndex();
564                    propertyStopClass = beanClass;
565                  }
566              }
567
568            if(explicitEventSetDescriptors == null) 
569              {
570                if(explicit.getEventSetDescriptors() != null) 
571                  {
572                    explicitEventSetDescriptors = explicit.getEventSetDescriptors();
573                    defaultEvent = explicit.getDefaultEventIndex();
574                    eventStopClass = beanClass;
575                  }
576              }
577
578            if(explicitMethodDescriptors == null) 
579              {
580                if(explicit.getMethodDescriptors() != null) 
581                  {
582                    explicitMethodDescriptors = explicit.getMethodDescriptors();
583                    methodStopClass = beanClass;
584                  }
585              }
586
587            if(im[0] == null && im[1] == null 
588               && im[2] == null && im[3] == null) 
589              {
590                im[0] = explicit.getIcon(0);
591                im[1] = explicit.getIcon(1);
592                im[2] = explicit.getIcon(2);
593                im[3] = explicit.getIcon(3);
594              }
595          }
596        beanClass = beanClass.getSuperclass();
597      }
598
599    if(propertyStopClass == null) 
600      {
601        propertyStopClass = stopClass;
602      }
603
604    if(eventStopClass == null) 
605      {
606        eventStopClass = stopClass;
607      }
608
609    if(methodStopClass == null) 
610      {
611        methodStopClass = stopClass;
612      }
613  }
614  
615  /** Throws away all cached data and makes sure we re-instantiate things
616    * like BeanDescriptors again.
617    */
618  static void flushCaches() {
619        explicitBeanInfos.clear();
620        emptyBeanInfos.clear();
621  }
622  
623  static BeanInfo findExplicitBeanInfo(Class beanClass) 
624  {
625    BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass);
626    if(retval != null) 
627      {
628        return retval;
629      } 
630    else if(emptyBeanInfos.indexOf(beanClass) != -1) 
631      {
632        return null;
633      } 
634    else 
635      {
636        retval = reallyFindExplicitBeanInfo(beanClass);
637        if(retval != null) 
638          {
639            explicitBeanInfos.put(beanClass,retval);
640          } 
641        else 
642          {
643            emptyBeanInfos.addElement(beanClass);
644          }
645        return retval;
646      }
647  }
648  
649  static BeanInfo reallyFindExplicitBeanInfo(Class beanClass) 
650  {
651    ClassLoader beanClassLoader = beanClass.getClassLoader();
652    BeanInfo beanInfo;
653
654    beanInfo = getBeanInfo(beanClassLoader, beanClass.getName() + "BeanInfo");
655    if (beanInfo == null)
656      {
657        String newName;
658        newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo";
659
660        for(int i = 0; i < Introspector.beanInfoSearchPath.length; i++) 
661          {
662            if (Introspector.beanInfoSearchPath[i].equals("")) 
663              beanInfo = getBeanInfo(beanClassLoader, newName);
664            else 
665              beanInfo = getBeanInfo(beanClassLoader,
666                                     Introspector.beanInfoSearchPath[i] + "."
667                                     + newName);
668
669                // Returns the beanInfo if it exists and the described class matches
670                // the one we searched.
671            if (beanInfo != null && beanInfo.getBeanDescriptor() != null &&
672                        beanInfo.getBeanDescriptor().getBeanClass() == beanClass)
673
674              return beanInfo;
675          }
676      }
677
678    return beanInfo;
679  }
680
681  /**
682   * Returns an instance of the given class name when it can be loaded
683   * through the given class loader, or null otherwise.
684   */
685  private static BeanInfo getBeanInfo(ClassLoader cl, String infoName)
686  {
687    try
688      {
689        return (BeanInfo) Class.forName(infoName, true, cl).newInstance();
690      }
691    catch (ClassNotFoundException cnfe)
692      {
693        return null;
694      }
695    catch (IllegalAccessException iae)
696      {
697        return null;
698      }
699    catch (InstantiationException ie)
700      {
701        return null;
702      }
703  }
704  
705}