OSDN Git Service

Jumbo patch:
[pf3gnuchains/gcc-fork.git] / libjava / java / beans / Introspector.java
1 /* java.beans.Introspector
2    Copyright (C) 1998 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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.
10  
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.
15
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
19 02111-1307 USA.
20
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. */
26
27
28 package java.beans;
29
30 import gnu.java.beans.*;
31 import java.util.*;
32 import java.lang.reflect.*;
33 import gnu.java.lang.*;
34
35 /**
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>
44  **
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>
49  **
50  ** <STRONG>Order of Operations</STRONG><P>
51  **
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>
58  **
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>
66  **
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>
71  **
72  ** <STRONG>Introspection Design Patterns</STRONG><P>
73  **
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
79  ** that order).
80  **
81  **
82  ** <STRONG>Properties:</STRONG><P>
83  ** 
84  ** <OL>
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
93  **     property.</LI>
94  ** <LI>If there is a
95  **     <CODE>public &lt;type&gt; getXXX(int)</CODE>
96  **     method, then XXX is a read-only indexed property of
97  **     type &lt;type&gt;.  If there is a
98  **     <CODE>public void setXXX(int,&lt;type&gt;)</CODE>
99  **     method, then it is a read-write indexed property of
100  **     type &lt;type&gt;.  There may also be a
101  **     <CODE>public &lt;type&gt;[] getXXX()</CODE> and a
102  **     <CODE>public void setXXX(&lt;type&gt;)</CODE>
103  **     method as well.</CODE></LI>
104  ** <LI>If there is a
105  **     <CODE>public void setXXX(int,&lt;type&gt;)</CODE>
106  **     method, then it is a write-only indexed property of
107  **     type &lt;type&gt;.  There may also be a
108  **     <CODE>public &lt;type&gt;[] getXXX()</CODE> and a
109  **     <CODE>public void setXXX(&lt;type&gt;)</CODE>
110  **     method as well.</CODE></LI>
111  ** <LI>If there is a
112  **     <CODE>public &lt;type&gt; getXXX()</CODE> method,
113  **     then XXX is a read-only property of type
114  **     &lt;type&gt;.  If there is a
115  **     <CODE>public void setXXX(&lt;type&gt;)</CODE>
116  **     method, then it will be used for the property and
117  **     the property will be considered read-write.</LI>
118  ** <LI>If there is a
119  **     <CODE>public void setXXX(&lt;type&gt;)</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 &lt;type&gt;.</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
130  **     properties.</LI>
131  ** </OL>
132  **
133  ** <STRONG>Events:</STRONG><P>
134  **
135  ** If there is a pair of methods,
136  ** <CODE>public void addXXX(&lt;type&gt;)</CODE> and
137  ** <CODE>public void removeXXX(&lt;type&gt;)</CODE>, where
138  ** &lt;type&gt; 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 &lt;type&gt;.<P>
142  **
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
147  ** sets.<P>
148  **
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>
154  **
155  ** <STRONG>Methods:</STRONG><P>
156  ** 
157  ** Any public methods (including those which were used
158  ** for Properties or Events) are used as Methods.
159  **
160  ** @author John Keiser
161  ** @since JDK1.1
162  ** @version 1.1.0, 29 Jul 1998
163  ** @see java.beans.BeanInfo
164  **/
165
166 public class Introspector {
167         static String[] beanInfoSearchPath = {"gnu.java.beans.info", "sun.beans.infos"};
168         static Hashtable beanInfoCache = new Hashtable();
169
170         private Introspector() {}
171
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.
178          **/
179         public static BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException {
180                 BeanInfo cachedInfo;
181                 synchronized(beanClass) {
182                 cachedInfo = (BeanInfo)beanInfoCache.get(beanClass);
183                 if(cachedInfo != null) {
184                         return cachedInfo;
185                 }
186                 cachedInfo = getBeanInfo(beanClass,null);
187                 beanInfoCache.put(beanClass,cachedInfo);
188                 return cachedInfo;
189                 }
190         }
191
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.
200          **/
201         public static BeanInfo getBeanInfo(Class beanClass, Class stopClass) throws IntrospectionException {
202                 ExplicitInfo explicit = new ExplicitInfo(beanClass,stopClass);
203
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());
209
210                 BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo();
211                 PropertyDescriptor[] p = explicit.explicitPropertyDescriptors;
212                 if(p!=null) {
213                         for(int i=0;i<p.length;i++) {
214                                 if(!currentInfo.hasProperty(p[i])) {
215                                         currentInfo.addProperty(p[i]);
216                                 }
217                         }
218                         if(explicit.defaultProperty != -1) {
219                                 currentInfo.setDefaultPropertyName(p[explicit.defaultProperty].getName());
220                         }
221                 }
222                 EventSetDescriptor[] e = explicit.explicitEventSetDescriptors;
223                 if(e!=null) {
224                         for(int i=0;i<e.length;i++) {
225                                 if(!currentInfo.hasEvent(e[i])) {
226                                         currentInfo.addEvent(e[i]);
227                                 }
228                         }
229                         if(explicit.defaultEvent != -1) {
230                                 currentInfo.setDefaultEventName(e[explicit.defaultEvent].getName());
231                         }
232                 }
233                 MethodDescriptor[] m = explicit.explicitMethodDescriptors;
234                 if(m!=null) {
235                         for(int i=0;i<m.length;i++) {
236                                 if(!currentInfo.hasMethod(m[i])) {
237                                         currentInfo.addMethod(m[i]);
238                                 }
239                         }
240                 }
241
242                 if(explicit.explicitBeanDescriptor != null) {
243                         currentInfo.setBeanDescriptor(new BeanDescriptor(beanClass,explicit.explicitBeanDescriptor.getCustomizerClass()));
244                 } else {
245                         currentInfo.setBeanDescriptor(new BeanDescriptor(beanClass,null));
246                 }
247
248                 currentInfo.setAdditionalBeanInfo(explicit.explicitBeanInfo);
249                 currentInfo.setIcons(explicit.im);
250
251                 return currentInfo.getBeanInfo();
252         }
253
254         /** Get the search path for BeanInfo classes.
255          ** @return the BeanInfo search path.
256          **/
257         public static String[] getBeanInfoSearchPath() {
258                 return beanInfoSearchPath;
259         }
260
261         /** Set the search path for BeanInfo classes.
262          ** @param beanInfoSearchPath the new BeanInfo search
263          **        path.
264          **/
265         public static void setBeanInfoSearchPath(String[] beanInfoSearchPath) {
266                 Introspector.beanInfoSearchPath = beanInfoSearchPath;
267         }
268
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.
276          **/
277         public static String decapitalize(String name) {
278                 try {
279                         if(!Character.isUpperCase(name.charAt(0))) {
280                                 return name;
281                         } else {
282                                 try {
283                                         if(Character.isUpperCase(name.charAt(1))) {
284                                                 return name;
285                                         } else {
286                                                 char[] c = name.toCharArray();
287                                                 c[0] = Character.toLowerCase(c[0]);
288                                                 return new String(c);
289                                         }
290                                 } catch(StringIndexOutOfBoundsException E) {
291                                         char[] c = new char[1];
292                                         c[0] = Character.toLowerCase(name.charAt(0));
293                                         return new String(c);
294                                 }
295                         }
296                 } catch(StringIndexOutOfBoundsException E) {
297                         return name;
298                 } catch(NullPointerException E) {
299                         return null;
300                 }
301         }
302
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);
307                 }
308                 return new ExplicitBeanInfo(b.getBeanDescriptor(),b.getAdditionalBeanInfo(),
309                                             b.getPropertyDescriptors(),b.getDefaultPropertyIndex(),
310                                             b.getEventSetDescriptors(),b.getDefaultEventIndex(),
311                                             b.getMethodDescriptors(),icons);
312         }
313 }
314
315 class ExplicitInfo {
316         BeanDescriptor explicitBeanDescriptor;
317         BeanInfo[] explicitBeanInfo;
318
319         PropertyDescriptor[] explicitPropertyDescriptors;
320         EventSetDescriptor[] explicitEventSetDescriptors;
321         MethodDescriptor[] explicitMethodDescriptors;
322
323         int defaultProperty;
324         int defaultEvent;
325
326         java.awt.Image[] im = new java.awt.Image[4];
327
328         Class propertyStopClass;
329         Class eventStopClass;
330         Class methodStopClass;
331
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();
338                                 }
339                                 if(explicitBeanInfo == null) {
340                                         explicitBeanInfo = explicit.getAdditionalBeanInfo();
341                                 }
342                                 if(explicitPropertyDescriptors == null) {
343                                         if(explicit.getPropertyDescriptors() != null) {
344                                                 explicitPropertyDescriptors = explicit.getPropertyDescriptors();
345                                                 defaultProperty = explicit.getDefaultPropertyIndex();
346                                                 propertyStopClass = beanClass;
347                                         }
348                                 }
349                                 if(explicitEventSetDescriptors == null) {
350                                         if(explicit.getEventSetDescriptors() != null) {
351                                                 explicitEventSetDescriptors = explicit.getEventSetDescriptors();
352                                                 defaultEvent = explicit.getDefaultEventIndex();
353                                                 eventStopClass = beanClass;
354                                         }
355                                 }
356                                 if(explicitMethodDescriptors == null) {
357                                         if(explicit.getMethodDescriptors() != null) {
358                                                 explicitMethodDescriptors = explicit.getMethodDescriptors();
359                                                 methodStopClass = beanClass;
360                                         }
361                                 }
362                                 if(im[0] == null
363                                    && im[1] == null
364                                    && im[2] == null
365                                    && im[3] == null) {
366                                         im[0] = explicit.getIcon(0);
367                                         im[1] = explicit.getIcon(1);
368                                         im[2] = explicit.getIcon(2);
369                                         im[3] = explicit.getIcon(3);
370                                 }
371                         }
372                         beanClass = beanClass.getSuperclass();
373                 }
374                 if(propertyStopClass == null) {
375                         propertyStopClass = stopClass;
376                 }
377                 if(eventStopClass == null) {
378                         eventStopClass = stopClass;
379                 }
380                 if(methodStopClass == null) {
381                         methodStopClass = stopClass;
382                 }
383         }
384
385         static Hashtable explicitBeanInfos = new Hashtable();
386         static Vector emptyBeanInfos = new Vector();
387
388         static BeanInfo findExplicitBeanInfo(Class beanClass) {
389                 BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass);
390                 if(retval != null) {
391                         return retval;
392                 } else if(emptyBeanInfos.indexOf(beanClass) != -1) {
393                         return null;
394                 } else {
395                         retval = reallyFindExplicitBeanInfo(beanClass);
396                         if(retval != null) {
397                                 explicitBeanInfos.put(beanClass,retval);
398                         } else {
399                                 emptyBeanInfos.addElement(beanClass);
400                         }
401                         return retval;
402                 }
403         }
404
405         static BeanInfo reallyFindExplicitBeanInfo(Class beanClass) {
406                 try {
407                 try {
408                         return (BeanInfo)Class.forName(beanClass.getName()+"BeanInfo").newInstance();
409                 } catch(ClassNotFoundException E) {
410                 }
411                 String newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo";
412                 for(int i=0;i<Introspector.beanInfoSearchPath.length;i++) {
413                         try {
414                                 if(Introspector.beanInfoSearchPath[i].equals("")) {
415                                         return (BeanInfo)Class.forName(newName).newInstance();
416                                 } else {
417                                         return (BeanInfo)Class.forName(Introspector.beanInfoSearchPath[i] + "." + newName).newInstance();
418                                 }
419                         } catch(ClassNotFoundException E) {
420                         }
421                 }
422                 } catch(IllegalAccessException E) {
423                 } catch(InstantiationException E) {
424                 }
425                 return null;
426         }
427 }