Merge from mainline
[official-gcc.git] / libjava / classpath / java / beans / Introspector.java
blob23c3cde5e3d699a76bd4bc4ad5242f950a3aeac7
1 /* java.beans.Introspector
2 Copyright (C) 1998, 2002, 2003 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java.beans;
41 import gnu.java.beans.BeanInfoEmbryo;
42 import gnu.java.beans.ExplicitBeanInfo;
43 import gnu.java.beans.IntrospectionIncubator;
44 import gnu.java.lang.ClassHelper;
46 import java.util.Hashtable;
47 import java.util.Vector;
49 /**
50 * Introspector is the class that does the bulk of the
51 * design-time work in Java Beans. Every class must have
52 * a BeanInfo in order for an RAD tool to use it; but, as
53 * promised, you don't have to write the BeanInfo class
54 * yourself if you don't want to. All you have to do is
55 * call getBeanInfo() in the Introspector and it will use
56 * standard JavaBeans-defined method signatures to
57 * determine the information about your class.<P>
59 * Don't worry about it too much, though: you can provide
60 * JavaBeans with as much customized information as you
61 * want, or as little as you want, using the BeanInfo
62 * interface (see BeanInfo for details).<P>
64 * <STRONG>Order of Operations</STRONG><P>
66 * When you call getBeanInfo(class c), the Introspector
67 * first searches for BeanInfo class to see if you
68 * provided any explicit information. It searches for a
69 * class named &lt;bean class name&gt;BeanInfo in different
70 * packages, first searching the bean class's package
71 * and then moving on to search the beanInfoSearchPath.<P>
73 * If it does not find a BeanInfo class, it acts as though
74 * it had found a BeanInfo class returning null from all
75 * methods (meaning it should discover everything through
76 * Introspection). If it does, then it takes the
77 * information it finds in the BeanInfo class to be
78 * canonical (that is, the information speaks for its
79 * class as well as all superclasses).<P>
81 * When it has introspected the class, calls
82 * getBeanInfo(c.getSuperclass) and adds that information
83 * to the information it has, not adding to any information
84 * it already has that is canonical.<P>
86 * <STRONG>Introspection Design Patterns</STRONG><P>
88 * When the Introspector goes in to read the class, it
89 * follows a well-defined order in order to not leave any
90 * methods unaccounted for. Its job is to step over all
91 * of the public methods in a class and determine whether
92 * they are part of a property, an event, or a method (in
93 * that order).
96 * <STRONG>Properties:</STRONG><P>
98 * <OL>
99 * <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>
147 * <STRONG>Events:</STRONG><P>
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>
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>
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>
169 * <STRONG>Methods:</STRONG><P>
171 * Any public methods (including those which were used
172 * for Properties or Events) are used as Methods.
174 * @author John Keiser
175 * @since JDK1.1
176 * @see java.beans.BeanInfo
178 public class Introspector {
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;
184 static String[] beanInfoSearchPath = {"gnu.java.beans.info"};
185 static Hashtable beanInfoCache = new Hashtable();
187 private Introspector() {}
189 /**
190 * Get the BeanInfo for class <CODE>beanClass</CODE>,
191 * first by looking for explicit information, next by
192 * using standard design patterns to determine
193 * information about the class.
195 * @param beanClass the class to get BeanInfo about.
196 * @return the BeanInfo object representing the class.
198 public static BeanInfo getBeanInfo(Class beanClass)
199 throws IntrospectionException
201 BeanInfo cachedInfo;
202 synchronized(beanClass)
204 cachedInfo = (BeanInfo)beanInfoCache.get(beanClass);
205 if(cachedInfo != null)
207 return cachedInfo;
209 cachedInfo = getBeanInfo(beanClass,null);
210 beanInfoCache.put(beanClass,cachedInfo);
211 return cachedInfo;
216 * Returns a {@BeanInfo} instance for the given Bean class where a flag
217 * controls the usage of explicit BeanInfo class to retrieve that
218 * information.
220 * <p>You have three options:</p>
221 * <p>With {@link #USE_ALL_BEANINFO} the result is the same as
222 * {@link #getBeanInfo(Class)}.</p>
224 * <p>Calling the method with <code>flag</code> set to
225 * {@link #IGNORE_IMMEDIATE_BEANINFO} will let it use all
226 * explicit BeanInfo classes for the beans superclasses
227 * but not for the bean class itself. Furthermore eventset,
228 * property and method information is retrieved by introspection
229 * if the explicit <code>BeanInfos</code> did not provide such data
230 * (ie. return <code>null</code> on {@link BeanInfo.getMethodDescriptors},
231 * {@link BeanInfo.getEventSetDescriptors} and
232 * {@link BeanInfo.getPropertyDescriptors}.)
233 * </p>
235 * <p>When the method is called with <code>flag</code< set to
236 * {@link #IGNORE_ALL_BEANINFO} all the bean data is retrieved
237 * by inspecting the class.</p>
239 * <p>Note: Any unknown value for <code>flag</code> is interpreted
240 * as {@link #IGNORE_ALL_BEANINFO}</p>.
242 * @param beanClass The class whose BeanInfo should be returned.
243 * @param flag Controls the usage of explicit <code>BeanInfo</code> classes.
244 * @return A BeanInfo object describing the class.
245 * @throws IntrospectionException If something goes wrong while retrieving
246 * the bean data.
248 public static BeanInfo getBeanInfo(Class beanClass, int flag)
249 throws IntrospectionException
251 IntrospectionIncubator ii;
252 BeanInfoEmbryo infoEmbryo;
254 switch(flag)
256 case USE_ALL_BEANINFO:
257 return getBeanInfo(beanClass);
258 case IGNORE_IMMEDIATE_BEANINFO:
259 Class superclass = beanClass.getSuperclass();
260 ExplicitInfo explicit = new ExplicitInfo(superclass, null);
262 ii = new IntrospectionIncubator();
263 if (explicit.explicitEventSetDescriptors != null)
264 ii.setEventStopClass(superclass);
266 if (explicit.explicitMethodDescriptors != null)
267 ii.setMethodStopClass(superclass);
269 if (explicit.explicitPropertyDescriptors != null)
270 ii.setPropertyStopClass(superclass);
272 ii.addMethods(beanClass.getMethods());
274 infoEmbryo = ii.getBeanInfoEmbryo();
275 merge(infoEmbryo, explicit);
277 infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null));
279 return infoEmbryo.getBeanInfo();
280 case IGNORE_ALL_BEANINFO:
281 default:
282 ii = new IntrospectionIncubator();
283 ii.addMethods(beanClass.getMethods());
284 infoEmbryo = ii.getBeanInfoEmbryo();
285 infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null));
287 return infoEmbryo.getBeanInfo();
292 * Flush all of the Introspector's internal caches.
294 * @since 1.2
296 public static void flushCaches()
298 beanInfoCache.clear();
300 // Clears all the intermediate ExplicitInfo instances which
301 // have been created.
302 // This makes sure we have to retrieve stuff like BeanDescriptors
303 // again. (Remember that FeatureDescriptor can be modified by the user.)
304 ExplicitInfo.flushCaches();
308 * Flush the Introspector's internal cached information for a given
309 * class.
311 * @param clz the class to be flushed.
312 * @throws NullPointerException if clz is null.
313 * @since 1.2
315 public static void flushFromCaches(Class clz)
317 synchronized (clz)
319 beanInfoCache.remove(clz);
323 /** Adds all explicity given bean info data to the introspected
324 * data.
326 * @param infoEmbryo Bean info data retrieved by introspection.
327 * @param explicit Bean info data retrieved by BeanInfo classes.
329 private static void merge(BeanInfoEmbryo infoEmbryo, ExplicitInfo explicit)
331 PropertyDescriptor[] p = explicit.explicitPropertyDescriptors;
332 if(p!=null)
334 for(int i=0;i<p.length;i++)
336 if(!infoEmbryo.hasProperty(p[i]))
338 infoEmbryo.addProperty(p[i]);
342 // -1 should be used to denote a missing default property but
343 // for robustness reasons any value below zero is discarded.
344 // Not doing so would let Classpath fail where the JDK succeeds.
345 if(explicit.defaultProperty > -1)
347 infoEmbryo.setDefaultPropertyName(p[explicit.defaultProperty].getName());
350 EventSetDescriptor[] e = explicit.explicitEventSetDescriptors;
351 if(e!=null)
353 for(int i=0;i<e.length;i++)
355 if(!infoEmbryo.hasEvent(e[i]))
357 infoEmbryo.addEvent(e[i]);
361 // -1 should be used to denote a missing default event but
362 // for robustness reasons any value below zero is discarded.
363 // Not doing so would let Classpath fail where the JDK succeeds.
364 if(explicit.defaultEvent > -1)
366 infoEmbryo.setDefaultEventName(e[explicit.defaultEvent].getName());
369 MethodDescriptor[] m = explicit.explicitMethodDescriptors;
370 if(m!=null)
372 for(int i=0;i<m.length;i++)
374 if(!infoEmbryo.hasMethod(m[i]))
376 infoEmbryo.addMethod(m[i]);
381 infoEmbryo.setAdditionalBeanInfo(explicit.explicitBeanInfo);
382 infoEmbryo.setIcons(explicit.im);
386 /**
387 * Get the BeanInfo for class <CODE>beanClass</CODE>,
388 * first by looking for explicit information, next by
389 * using standard design patterns to determine
390 * information about the class. It crawls up the
391 * inheritance tree until it hits <CODE>topClass</CODE>.
393 * @param beanClass the Bean class.
394 * @param stopClass the class to stop at.
395 * @return the BeanInfo object representing the class.
397 public static BeanInfo getBeanInfo(Class beanClass, Class stopClass)
398 throws IntrospectionException
400 ExplicitInfo explicit = new ExplicitInfo(beanClass, stopClass);
402 IntrospectionIncubator ii = new IntrospectionIncubator();
403 ii.setPropertyStopClass(explicit.propertyStopClass);
404 ii.setEventStopClass(explicit.eventStopClass);
405 ii.setMethodStopClass(explicit.methodStopClass);
406 ii.addMethods(beanClass.getMethods());
408 BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo();
410 merge(currentInfo, explicit);
412 // Sets the info's BeanDescriptor to the one we extracted from the
413 // explicit BeanInfo instance(s) if they contained one. Otherwise we
414 // create the BeanDescriptor from scratch.
415 // Note: We do not create a copy the retrieved BeanDescriptor which will allow
416 // the user to modify the instance while it is cached. However this is how
417 // the RI does it.
418 currentInfo.setBeanDescriptor(
419 (explicit.explicitBeanDescriptor == null ?
420 new BeanDescriptor(beanClass, null) :
421 explicit.explicitBeanDescriptor));
422 return currentInfo.getBeanInfo();
425 /**
426 * Get the search path for BeanInfo classes.
428 * @return the BeanInfo search path.
430 public static String[] getBeanInfoSearchPath()
432 return beanInfoSearchPath;
435 /**
436 * Set the search path for BeanInfo classes.
437 * @param beanInfoSearchPath the new BeanInfo search
438 * path.
440 public static void setBeanInfoSearchPath(String[] beanInfoSearchPath)
442 Introspector.beanInfoSearchPath = beanInfoSearchPath;
445 /**
446 * A helper method to convert a name to standard Java
447 * naming conventions: anything with two capitals as the
448 * first two letters remains the same, otherwise the
449 * first letter is decapitalized. URL = URL, I = i,
450 * MyMethod = myMethod.
452 * @param name the name to decapitalize.
453 * @return the decapitalized name.
455 public static String decapitalize(String name)
457 try
459 if(!Character.isUpperCase(name.charAt(0)))
461 return name;
463 else
465 try
467 if(Character.isUpperCase(name.charAt(1)))
469 return name;
471 else
473 char[] c = name.toCharArray();
474 c[0] = Character.toLowerCase(c[0]);
475 return new String(c);
478 catch(StringIndexOutOfBoundsException E)
480 char[] c = new char[1];
481 c[0] = Character.toLowerCase(name.charAt(0));
482 return new String(c);
486 catch(StringIndexOutOfBoundsException E)
488 return name;
490 catch(NullPointerException E)
492 return null;
496 static BeanInfo copyBeanInfo(BeanInfo b)
498 java.awt.Image[] icons = new java.awt.Image[4];
499 for(int i=1;i<=4;i++)
501 icons[i-1] = b.getIcon(i);
504 return new ExplicitBeanInfo(b.getBeanDescriptor(),
505 b.getAdditionalBeanInfo(),
506 b.getPropertyDescriptors(),
507 b.getDefaultPropertyIndex(),
508 b.getEventSetDescriptors(),
509 b.getDefaultEventIndex(),
510 b.getMethodDescriptors(),
511 icons);
515 class ExplicitInfo
517 BeanDescriptor explicitBeanDescriptor;
518 BeanInfo[] explicitBeanInfo;
520 PropertyDescriptor[] explicitPropertyDescriptors;
521 EventSetDescriptor[] explicitEventSetDescriptors;
522 MethodDescriptor[] explicitMethodDescriptors;
524 int defaultProperty;
525 int defaultEvent;
527 java.awt.Image[] im = new java.awt.Image[4];
529 Class propertyStopClass;
530 Class eventStopClass;
531 Class methodStopClass;
533 static Hashtable explicitBeanInfos = new Hashtable();
534 static Vector emptyBeanInfos = new Vector();
536 ExplicitInfo(Class beanClass, Class stopClass)
538 while(beanClass != null && !beanClass.equals(stopClass))
541 BeanInfo explicit = findExplicitBeanInfo(beanClass);
544 if(explicit != null)
547 if(explicitBeanDescriptor == null)
549 explicitBeanDescriptor = explicit.getBeanDescriptor();
552 if(explicitBeanInfo == null)
554 explicitBeanInfo = explicit.getAdditionalBeanInfo();
557 if(explicitPropertyDescriptors == null)
559 if(explicit.getPropertyDescriptors() != null)
561 explicitPropertyDescriptors = explicit.getPropertyDescriptors();
562 defaultProperty = explicit.getDefaultPropertyIndex();
563 propertyStopClass = beanClass;
567 if(explicitEventSetDescriptors == null)
569 if(explicit.getEventSetDescriptors() != null)
571 explicitEventSetDescriptors = explicit.getEventSetDescriptors();
572 defaultEvent = explicit.getDefaultEventIndex();
573 eventStopClass = beanClass;
577 if(explicitMethodDescriptors == null)
579 if(explicit.getMethodDescriptors() != null)
581 explicitMethodDescriptors = explicit.getMethodDescriptors();
582 methodStopClass = beanClass;
586 if(im[0] == null && im[1] == null
587 && im[2] == null && im[3] == null)
589 im[0] = explicit.getIcon(0);
590 im[1] = explicit.getIcon(1);
591 im[2] = explicit.getIcon(2);
592 im[3] = explicit.getIcon(3);
595 beanClass = beanClass.getSuperclass();
598 if(propertyStopClass == null)
600 propertyStopClass = stopClass;
603 if(eventStopClass == null)
605 eventStopClass = stopClass;
608 if(methodStopClass == null)
610 methodStopClass = stopClass;
614 /** Throws away all cached data and makes sure we re-instantiate things
615 * like BeanDescriptors again.
617 static void flushCaches() {
618 explicitBeanInfos.clear();
619 emptyBeanInfos.clear();
622 static BeanInfo findExplicitBeanInfo(Class beanClass)
624 BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass);
625 if(retval != null)
627 return retval;
629 else if(emptyBeanInfos.indexOf(beanClass) != -1)
631 return null;
633 else
635 retval = reallyFindExplicitBeanInfo(beanClass);
636 if(retval != null)
638 explicitBeanInfos.put(beanClass,retval);
640 else
642 emptyBeanInfos.addElement(beanClass);
644 return retval;
648 static BeanInfo reallyFindExplicitBeanInfo(Class beanClass)
650 ClassLoader beanClassLoader = beanClass.getClassLoader();
651 BeanInfo beanInfo;
653 beanInfo = getBeanInfo(beanClassLoader, beanClass.getName() + "BeanInfo");
654 if (beanInfo == null)
656 String newName;
657 newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo";
659 for(int i = 0; i < Introspector.beanInfoSearchPath.length; i++)
661 if (Introspector.beanInfoSearchPath[i].equals(""))
662 beanInfo = getBeanInfo(beanClassLoader, newName);
663 else
664 beanInfo = getBeanInfo(beanClassLoader,
665 Introspector.beanInfoSearchPath[i] + "."
666 + newName);
668 // Returns the beanInfo if it exists and the described class matches
669 // the one we searched.
670 if (beanInfo != null && beanInfo.getBeanDescriptor() != null &&
671 beanInfo.getBeanDescriptor().getBeanClass() == beanClass)
673 return beanInfo;
677 return beanInfo;
681 * Returns an instance of the given class name when it can be loaded
682 * through the given class loader, or null otherwise.
684 private static BeanInfo getBeanInfo(ClassLoader cl, String infoName)
688 return (BeanInfo) Class.forName(infoName, true, cl).newInstance();
690 catch (ClassNotFoundException cnfe)
692 return null;
694 catch (IllegalAccessException iae)
696 return null;
698 catch (InstantiationException ie)
700 return null;