1 /* java.beans.Introspector
2 Copyright (C) 1998 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)
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., 59 Temple Place, Suite 330, Boston, MA
21 As a special exception, if you link this library with other files to
22 produce an executable, this library does not by itself cause the
23 resulting executable to be covered by the GNU General Public License.
24 This exception does not however invalidate any other reasons why the
25 executable file might be covered by the GNU General Public License. */
30 import gnu.java.beans.*;
32 import java.lang.reflect.*;
33 import gnu.java.lang.*;
36 ** Introspector is the class that does the bulk of the
37 ** design-time work in Java Beans. Every class must have
38 ** a BeanInfo in order for an RAD tool to use it; but, as
39 ** promised, you don't have to write the BeanInfo class
40 ** yourself if you don't want to. All you have to do is
41 ** call getBeanInfo() in the Introspector and it will use
42 ** standard JavaBeans-defined method signatures to
43 ** determine the information about your class.<P>
45 ** Don't worry about it too much, though: you can provide
46 ** JavaBeans with as much customized information as you
47 ** want, or as little as you want, using the BeanInfo
48 ** interface (see BeanInfo for details).<P>
50 ** <STRONG>Order of Operations</STRONG><P>
52 ** When you call getBeanInfo(class c), the Introspector
53 ** first searches for BeanInfo class to see if you
54 ** provided any explicit information. It searches for a
55 ** class named <bean class name>BeanInfo in different
56 ** packages, first searching the bean class's package
57 ** and then moving on to search the beanInfoSearchPath.<P>
59 ** If it does not find a BeanInfo class, it acts as though
60 ** it had found a BeanInfo class returning null from all
61 ** methods (meaning it should discover everything through
62 ** Introspection). If it does, then it takes the
63 ** information it finds in the BeanInfo class to be
64 ** canonical (that is, the information speaks for its
65 ** class as well as all superclasses).<P>
67 ** When it has introspected the class, calls
68 ** getBeanInfo(c.getSuperclass) and adds that information
69 ** to the information it has, not adding to any information
70 ** it already has that is canonical.<P>
72 ** <STRONG>Introspection Design Patterns</STRONG><P>
74 ** When the Introspector goes in to read the class, it
75 ** follows a well-defined order in order to not leave any
76 ** methods unaccounted for. Its job is to step over all
77 ** of the public methods in a class and determine whether
78 ** they are part of a property, an event, or a method (in
82 ** <STRONG>Properties:</STRONG><P>
85 ** <LI>If there is a <CODE>public boolean isXXX()</CODE>
86 ** method, then XXX is a read-only boolean property.
87 ** <CODE>boolean getXXX()</CODE> may be supplied in
88 ** addition to this method, although isXXX() is the
89 ** one that will be used in this case and getXXX()
90 ** will be ignored. If there is a
91 ** <CODE>public void setXXX(boolean)</CODE> method,
92 ** it is part of this group and makes it a read-write
95 ** <CODE>public <type> getXXX(int)</CODE>
96 ** method, then XXX is a read-only indexed property of
97 ** type <type>. If there is a
98 ** <CODE>public void setXXX(int,<type>)</CODE>
99 ** method, then it is a read-write indexed property of
100 ** type <type>. There may also be a
101 ** <CODE>public <type>[] getXXX()</CODE> and a
102 ** <CODE>public void setXXX(<type>)</CODE>
103 ** method as well.</CODE></LI>
105 ** <CODE>public void setXXX(int,<type>)</CODE>
106 ** method, then it is a write-only indexed property of
107 ** type <type>. There may also be a
108 ** <CODE>public <type>[] getXXX()</CODE> and a
109 ** <CODE>public void setXXX(<type>)</CODE>
110 ** method as well.</CODE></LI>
112 ** <CODE>public <type> getXXX()</CODE> method,
113 ** then XXX is a read-only property of type
114 ** <type>. If there is a
115 ** <CODE>public void setXXX(<type>)</CODE>
116 ** method, then it will be used for the property and
117 ** the property will be considered read-write.</LI>
119 ** <CODE>public void setXXX(<type>)</CODE>
120 ** method, then as long as XXX is not already used as
121 ** the name of a property, XXX is assumed to be a
122 ** write-only property of type <type>.</LI>
123 ** <LI>In all of the above cases, if the setXXX() method
124 ** throws <CODE>PropertyVetoException</CODE>, then the
125 ** property in question is assumed to be constrained.
126 ** No properties are ever assumed to be bound
127 ** (<STRONG>Spec Note:</STRONG> this is not in the
128 ** spec, it just makes sense). See PropertyDescriptor
129 ** for a description of bound and constrained
133 ** <STRONG>Events:</STRONG><P>
135 ** If there is a pair of methods,
136 ** <CODE>public void addXXX(<type>)</CODE> and
137 ** <CODE>public void removeXXX(<type>)</CODE>, where
138 ** <type> is a descendant of
139 ** <CODE>java.util.EventListener</CODE>, then the pair of
140 ** methods imply that this Bean will fire events to
141 ** listeners of type <type>.<P>
143 ** If the addXXX() method throws
144 ** <CODE>java.util.TooManyListenersException</CODE>, then
145 ** the event set is assumed to be <EM>unicast</EM>. See
146 ** EventSetDescriptor for a discussion of unicast event
149 ** <STRONG>Spec Note:</STRONG> the spec seems to say that
150 ** the listener type's classname must be equal to the XXX
151 ** part of addXXX() and removeXXX(), but that is not the
152 ** case in Sun's implementation, so I am assuming it is
153 ** not the case in general.<P>
155 ** <STRONG>Methods:</STRONG><P>
157 ** Any public methods (including those which were used
158 ** for Properties or Events) are used as Methods.
160 ** @author John Keiser
162 ** @version 1.1.0, 29 Jul 1998
163 ** @see java.beans.BeanInfo
166 public class Introspector {
167 static String[] beanInfoSearchPath = {"gnu.java.beans.info", "sun.beans.infos"};
168 static Hashtable beanInfoCache = new Hashtable();
170 private Introspector() {}
172 /** Get the BeanInfo for class <CODE>beanClass</CODE>,
173 ** first by looking for explicit information, next by
174 ** using standard design patterns to determine
175 ** information about the class.
176 ** @param beanClass the class to get BeanInfo about.
177 ** @return the BeanInfo object representing the class.
179 public static BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException {
181 synchronized(beanClass) {
182 cachedInfo = (BeanInfo)beanInfoCache.get(beanClass);
183 if(cachedInfo != null) {
186 cachedInfo = getBeanInfo(beanClass,null);
187 beanInfoCache.put(beanClass,cachedInfo);
192 /** Get the BeanInfo for class <CODE>beanClass</CODE>,
193 ** first by looking for explicit information, next by
194 ** using standard design patterns to determine
195 ** information about the class. It crawls up the
196 ** inheritance tree until it hits <CODE>topClass</CODE>.
197 ** @param beanClass the Bean class.
198 ** @param stopClass the class to stop at.
199 ** @return the BeanInfo object representing the class.
201 public static BeanInfo getBeanInfo(Class beanClass, Class stopClass) throws IntrospectionException {
202 ExplicitInfo explicit = new ExplicitInfo(beanClass,stopClass);
204 IntrospectionIncubator ii = new IntrospectionIncubator();
205 ii.setPropertyStopClass(explicit.propertyStopClass);
206 ii.setEventStopClass(explicit.eventStopClass);
207 ii.setMethodStopClass(explicit.methodStopClass);
208 ii.addMethods(beanClass.getMethods());
210 BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo();
211 PropertyDescriptor[] p = explicit.explicitPropertyDescriptors;
213 for(int i=0;i<p.length;i++) {
214 if(!currentInfo.hasProperty(p[i])) {
215 currentInfo.addProperty(p[i]);
218 if(explicit.defaultProperty != -1) {
219 currentInfo.setDefaultPropertyName(p[explicit.defaultProperty].getName());
222 EventSetDescriptor[] e = explicit.explicitEventSetDescriptors;
224 for(int i=0;i<e.length;i++) {
225 if(!currentInfo.hasEvent(e[i])) {
226 currentInfo.addEvent(e[i]);
229 if(explicit.defaultEvent != -1) {
230 currentInfo.setDefaultEventName(e[explicit.defaultEvent].getName());
233 MethodDescriptor[] m = explicit.explicitMethodDescriptors;
235 for(int i=0;i<m.length;i++) {
236 if(!currentInfo.hasMethod(m[i])) {
237 currentInfo.addMethod(m[i]);
242 if(explicit.explicitBeanDescriptor != null) {
243 currentInfo.setBeanDescriptor(new BeanDescriptor(beanClass,explicit.explicitBeanDescriptor.getCustomizerClass()));
245 currentInfo.setBeanDescriptor(new BeanDescriptor(beanClass,null));
248 currentInfo.setAdditionalBeanInfo(explicit.explicitBeanInfo);
249 currentInfo.setIcons(explicit.im);
251 return currentInfo.getBeanInfo();
254 /** Get the search path for BeanInfo classes.
255 ** @return the BeanInfo search path.
257 public static String[] getBeanInfoSearchPath() {
258 return beanInfoSearchPath;
261 /** Set the search path for BeanInfo classes.
262 ** @param beanInfoSearchPath the new BeanInfo search
265 public static void setBeanInfoSearchPath(String[] beanInfoSearchPath) {
266 Introspector.beanInfoSearchPath = beanInfoSearchPath;
269 /** A helper method to convert a name to standard Java
270 ** naming conventions: anything with two capitals as the
271 ** first two letters remains the same, otherwise the
272 ** first letter is decapitalized. URL = URL, I = i,
273 ** MyMethod = myMethod.
274 ** @param name the name to decapitalize.
275 ** @return the decapitalized name.
277 public static String decapitalize(String name) {
279 if(!Character.isUpperCase(name.charAt(0))) {
283 if(Character.isUpperCase(name.charAt(1))) {
286 char[] c = name.toCharArray();
287 c[0] = Character.toLowerCase(c[0]);
288 return new String(c);
290 } catch(StringIndexOutOfBoundsException E) {
291 char[] c = new char[1];
292 c[0] = Character.toLowerCase(name.charAt(0));
293 return new String(c);
296 } catch(StringIndexOutOfBoundsException E) {
298 } catch(NullPointerException E) {
303 static BeanInfo copyBeanInfo(BeanInfo b) {
304 java.awt.Image[] icons = new java.awt.Image[4];
305 for(int i=1;i<=4;i++) {
306 icons[i-1] = b.getIcon(i);
308 return new ExplicitBeanInfo(b.getBeanDescriptor(),b.getAdditionalBeanInfo(),
309 b.getPropertyDescriptors(),b.getDefaultPropertyIndex(),
310 b.getEventSetDescriptors(),b.getDefaultEventIndex(),
311 b.getMethodDescriptors(),icons);
316 BeanDescriptor explicitBeanDescriptor;
317 BeanInfo[] explicitBeanInfo;
319 PropertyDescriptor[] explicitPropertyDescriptors;
320 EventSetDescriptor[] explicitEventSetDescriptors;
321 MethodDescriptor[] explicitMethodDescriptors;
326 java.awt.Image[] im = new java.awt.Image[4];
328 Class propertyStopClass;
329 Class eventStopClass;
330 Class methodStopClass;
332 ExplicitInfo(Class beanClass, Class stopClass) {
333 while(beanClass != null && !beanClass.equals(stopClass)) {
334 BeanInfo explicit = findExplicitBeanInfo(beanClass);
335 if(explicit != null) {
336 if(explicitBeanDescriptor == null) {
337 explicitBeanDescriptor = explicit.getBeanDescriptor();
339 if(explicitBeanInfo == null) {
340 explicitBeanInfo = explicit.getAdditionalBeanInfo();
342 if(explicitPropertyDescriptors == null) {
343 if(explicit.getPropertyDescriptors() != null) {
344 explicitPropertyDescriptors = explicit.getPropertyDescriptors();
345 defaultProperty = explicit.getDefaultPropertyIndex();
346 propertyStopClass = beanClass;
349 if(explicitEventSetDescriptors == null) {
350 if(explicit.getEventSetDescriptors() != null) {
351 explicitEventSetDescriptors = explicit.getEventSetDescriptors();
352 defaultEvent = explicit.getDefaultEventIndex();
353 eventStopClass = beanClass;
356 if(explicitMethodDescriptors == null) {
357 if(explicit.getMethodDescriptors() != null) {
358 explicitMethodDescriptors = explicit.getMethodDescriptors();
359 methodStopClass = beanClass;
366 im[0] = explicit.getIcon(0);
367 im[1] = explicit.getIcon(1);
368 im[2] = explicit.getIcon(2);
369 im[3] = explicit.getIcon(3);
372 beanClass = beanClass.getSuperclass();
374 if(propertyStopClass == null) {
375 propertyStopClass = stopClass;
377 if(eventStopClass == null) {
378 eventStopClass = stopClass;
380 if(methodStopClass == null) {
381 methodStopClass = stopClass;
385 static Hashtable explicitBeanInfos = new Hashtable();
386 static Vector emptyBeanInfos = new Vector();
388 static BeanInfo findExplicitBeanInfo(Class beanClass) {
389 BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass);
392 } else if(emptyBeanInfos.indexOf(beanClass) != -1) {
395 retval = reallyFindExplicitBeanInfo(beanClass);
397 explicitBeanInfos.put(beanClass,retval);
399 emptyBeanInfos.addElement(beanClass);
405 static BeanInfo reallyFindExplicitBeanInfo(Class beanClass) {
408 return (BeanInfo)Class.forName(beanClass.getName()+"BeanInfo").newInstance();
409 } catch(ClassNotFoundException E) {
411 String newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo";
412 for(int i=0;i<Introspector.beanInfoSearchPath.length;i++) {
414 if(Introspector.beanInfoSearchPath[i].equals("")) {
415 return (BeanInfo)Class.forName(newName).newInstance();
417 return (BeanInfo)Class.forName(Introspector.beanInfoSearchPath[i] + "." + newName).newInstance();
419 } catch(ClassNotFoundException E) {
422 } catch(IllegalAccessException E) {
423 } catch(InstantiationException E) {