KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > beans > Introspector


1 /*
2  * @(#)Introspector.java 1.143 05/08/09
3  *
4  * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.beans;
9
10 import java.lang.ref.Reference JavaDoc;
11 import java.lang.ref.SoftReference JavaDoc;
12
13 import java.lang.reflect.Method JavaDoc;
14 import java.lang.reflect.Modifier JavaDoc;
15
16 import java.security.AccessController JavaDoc;
17 import java.security.PrivilegedAction JavaDoc;
18
19 import java.util.Collections JavaDoc;
20 import java.util.Map JavaDoc;
21 import java.util.ArrayList JavaDoc;
22 import java.util.HashMap JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.EventListener JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.WeakHashMap JavaDoc;
27 import java.util.TreeMap JavaDoc;
28 import sun.reflect.misc.ReflectUtil;
29
30 /**
31  * The Introspector class provides a standard way for tools to learn about
32  * the properties, events, and methods supported by a target Java Bean.
33  * <p>
34  * For each of those three kinds of information, the Introspector will
35  * separately analyze the bean's class and superclasses looking for
36  * either explicit or implicit information and use that information to
37  * build a BeanInfo object that comprehensively describes the target bean.
38  * <p>
39  * For each class "Foo", explicit information may be available if there exists
40  * a corresponding "FooBeanInfo" class that provides a non-null value when
41  * queried for the information. We first look for the BeanInfo class by
42  * taking the full package-qualified name of the target bean class and
43  * appending "BeanInfo" to form a new class name. If this fails, then
44  * we take the final classname component of this name, and look for that
45  * class in each of the packages specified in the BeanInfo package search
46  * path.
47  * <p>
48  * Thus for a class such as "sun.xyz.OurButton" we would first look for a
49  * BeanInfo class called "sun.xyz.OurButtonBeanInfo" and if that failed we'd
50  * look in each package in the BeanInfo search path for an OurButtonBeanInfo
51  * class. With the default search path, this would mean looking for
52  * "sun.beans.infos.OurButtonBeanInfo".
53  * <p>
54  * If a class provides explicit BeanInfo about itself then we add that to
55  * the BeanInfo information we obtained from analyzing any derived classes,
56  * but we regard the explicit information as being definitive for the current
57  * class and its base classes, and do not proceed any further up the superclass
58  * chain.
59  * <p>
60  * If we don't find explicit BeanInfo on a class, we use low-level
61  * reflection to study the methods of the class and apply standard design
62  * patterns to identify property accessors, event sources, or public
63  * methods. We then proceed to analyze the class's superclass and add
64  * in the information from it (and possibly on up the superclass chain).
65  *
66  * <p>
67  * Because the Introspector caches BeanInfo classes for better performance,
68  * take care if you use it in an application that uses
69  * multiple class loaders.
70  * In general, when you destroy a <code>ClassLoader</code>
71  * that has been used to introspect classes,
72  * you should use the
73  * {@link #flushCaches <code>Introspector.flushCaches</code>}
74  * or
75  * {@link #flushFromCaches <code>Introspector.flushFromCaches</code>} method
76  * to flush all of the introspected classes out of the cache.
77  *
78  * <P>
79  * For more information about introspection and design patterns, please
80  * consult the
81  * <a HREF="http://java.sun.com/products/javabeans/docs/index.html">JavaBeans specification</a>.
82  */

83
84 public class Introspector {
85
86     // Flags that can be used to control getBeanInfo:
87
public final static int USE_ALL_BEANINFO = 1;
88     public final static int IGNORE_IMMEDIATE_BEANINFO = 2;
89     public final static int IGNORE_ALL_BEANINFO = 3;
90
91     // Static Caches to speed up introspection.
92
private static Map JavaDoc declaredMethodCache =
93     Collections.synchronizedMap(new WeakHashMap JavaDoc());
94     private static Map JavaDoc beanInfoCache =
95     Collections.synchronizedMap(new WeakHashMap JavaDoc());
96
97     private Class JavaDoc beanClass;
98     private BeanInfo JavaDoc explicitBeanInfo;
99     private BeanInfo JavaDoc superBeanInfo;
100     private BeanInfo JavaDoc additionalBeanInfo[];
101
102     private boolean propertyChangeSource = false;
103     private static Class JavaDoc eventListenerType = EventListener JavaDoc.class;
104
105     // These should be removed.
106
private String JavaDoc defaultEventName;
107     private String JavaDoc defaultPropertyName;
108     private int defaultEventIndex = -1;
109     private int defaultPropertyIndex = -1;
110
111     // Methods maps from Method objects to MethodDescriptors
112
private Map JavaDoc methods;
113
114     // properties maps from String names to PropertyDescriptors
115
private Map JavaDoc properties;
116
117     // events maps from String names to EventSetDescriptors
118
private Map JavaDoc events;
119
120     private final static String JavaDoc DEFAULT_INFO_PATH = "sun.beans.infos";
121
122     private static String JavaDoc[] searchPath = { DEFAULT_INFO_PATH };
123
124     private final static EventSetDescriptor JavaDoc[] EMPTY_EVENTSETDESCRIPTORS = new EventSetDescriptor JavaDoc[0];
125
126     private static final String JavaDoc ADD_PREFIX = "add";
127     private static final String JavaDoc REMOVE_PREFIX = "remove";
128     private static final String JavaDoc GET_PREFIX = "get";
129     private static final String JavaDoc SET_PREFIX = "set";
130     private static final String JavaDoc IS_PREFIX = "is";
131     private static final String JavaDoc BEANINFO_SUFFIX = "BeanInfo";
132
133     //======================================================================
134
// Public methods
135
//======================================================================
136

137     /**
138      * Introspect on a Java Bean and learn about all its properties, exposed
139      * methods, and events.
140      * <p>
141      * If the BeanInfo class for a Java Bean has been previously Introspected
142      * then the BeanInfo class is retrieved from the BeanInfo cache.
143      *
144      * @param beanClass The bean class to be analyzed.
145      * @return A BeanInfo object describing the target bean.
146      * @exception IntrospectionException if an exception occurs during
147      * introspection.
148      * @see #flushCaches
149      * @see #flushFromCaches
150      */

151     public static BeanInfo JavaDoc getBeanInfo(Class JavaDoc<?> beanClass)
152     throws IntrospectionException JavaDoc
153     {
154     if (!ReflectUtil.isPackageAccessible(beanClass)) {
155         return (new Introspector JavaDoc(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
156     }
157     BeanInfo JavaDoc bi = (BeanInfo JavaDoc)beanInfoCache.get(beanClass);
158     if (bi == null) {
159         bi = (new Introspector JavaDoc(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
160         beanInfoCache.put(beanClass, bi);
161     }
162     return bi;
163     }
164
165     /**
166      * Introspect on a Java bean and learn about all its properties, exposed
167      * methods, and events, subject to some control flags.
168      * <p>
169      * If the BeanInfo class for a Java Bean has been previously Introspected
170      * based on the same arguments then the BeanInfo class is retrieved
171      * from the BeanInfo cache.
172      *
173      * @param beanClass The bean class to be analyzed.
174      * @param flags Flags to control the introspection.
175      * If flags == USE_ALL_BEANINFO then we use all of the BeanInfo
176      * classes we can discover.
177      * If flags == IGNORE_IMMEDIATE_BEANINFO then we ignore any
178      * BeanInfo associated with the specified beanClass.
179      * If flags == IGNORE_ALL_BEANINFO then we ignore all BeanInfo
180      * associated with the specified beanClass or any of its
181      * parent classes.
182      * @return A BeanInfo object describing the target bean.
183      * @exception IntrospectionException if an exception occurs during
184      * introspection.
185      */

186     public static BeanInfo JavaDoc getBeanInfo(Class JavaDoc<?> beanClass, int flags)
187                         throws IntrospectionException JavaDoc {
188     return getBeanInfo(beanClass, null, flags);
189     }
190
191     /**
192      * Introspect on a Java bean and learn all about its properties, exposed
193      * methods, below a given "stop" point.
194      * <p>
195      * If the BeanInfo class for a Java Bean has been previously Introspected
196      * based on the same arguments, then the BeanInfo class is retrieved
197      * from the BeanInfo cache.
198      *
199      * @param beanClass The bean class to be analyzed.
200      * @param stopClass The baseclass at which to stop the analysis. Any
201      * methods/properties/events in the stopClass or in its baseclasses
202      * will be ignored in the analysis.
203      * @exception IntrospectionException if an exception occurs during
204      * introspection.
205      */

206     public static BeanInfo JavaDoc getBeanInfo(Class JavaDoc<?> beanClass, Class JavaDoc<?> stopClass)
207                         throws IntrospectionException JavaDoc {
208     return getBeanInfo(beanClass, stopClass, USE_ALL_BEANINFO);
209     }
210
211     /**
212      * Only called from the public getBeanInfo methods. This method caches
213      * the Introspected BeanInfo based on the arguments.
214      */

215     private static BeanInfo JavaDoc getBeanInfo(Class JavaDoc beanClass, Class JavaDoc stopClass,
216                     int flags) throws IntrospectionException JavaDoc {
217     BeanInfo JavaDoc bi;
218     if (stopClass == null && flags == USE_ALL_BEANINFO) {
219         // Same parameters to take advantage of caching.
220
bi = getBeanInfo(beanClass);
221     } else {
222         bi = (new Introspector JavaDoc(beanClass, stopClass, flags)).getBeanInfo();
223     }
224     return bi;
225
226     // Old behaviour: Make an independent copy of the BeanInfo.
227
//return new GenericBeanInfo(bi);
228
}
229
230
231     /**
232      * Utility method to take a string and convert it to normal Java variable
233      * name capitalization. This normally means converting the first
234      * character from upper case to lower case, but in the (unusual) special
235      * case when there is more than one character and both the first and
236      * second characters are upper case, we leave it alone.
237      * <p>
238      * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
239      * as "URL".
240      *
241      * @param name The string to be decapitalized.
242      * @return The decapitalized version of the string.
243      */

244     public static String JavaDoc decapitalize(String JavaDoc name) {
245     if (name == null || name.length() == 0) {
246         return name;
247     }
248     if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
249             Character.isUpperCase(name.charAt(0))){
250         return name;
251     }
252     char chars[] = name.toCharArray();
253     chars[0] = Character.toLowerCase(chars[0]);
254     return new String JavaDoc(chars);
255     }
256
257     /**
258      * Gets the list of package names that will be used for
259      * finding BeanInfo classes.
260      *
261      * @return The array of package names that will be searched in
262      * order to find BeanInfo classes. The default value
263      * for this array is implementation-dependent; e.g.
264      * Sun implementation initially sets to {"sun.beans.infos"}.
265      */

266
267     public static synchronized String JavaDoc[] getBeanInfoSearchPath() {
268     // Return a copy of the searchPath.
269
String JavaDoc result[] = new String JavaDoc[searchPath.length];
270     for (int i = 0; i < searchPath.length; i++) {
271         result[i] = searchPath[i];
272     }
273     return result;
274     }
275
276     /**
277      * Change the list of package names that will be used for
278      * finding BeanInfo classes. The behaviour of
279      * this method is undefined if parameter path
280      * is null.
281      *
282      * <p>First, if there is a security manager, its <code>checkPropertiesAccess</code>
283      * method is called. This could result in a SecurityException.
284      *
285      * @param path Array of package names.
286      * @exception SecurityException if a security manager exists and its
287      * <code>checkPropertiesAccess</code> method doesn't allow setting
288      * of system properties.
289      * @see SecurityManager#checkPropertiesAccess
290      */

291
292     public static synchronized void setBeanInfoSearchPath(String JavaDoc path[]) {
293     SecurityManager JavaDoc sm = System.getSecurityManager();
294     if (sm != null) {
295         sm.checkPropertiesAccess();
296     }
297     searchPath = path;
298     }
299
300
301     /**
302      * Flush all of the Introspector's internal caches. This method is
303      * not normally required. It is normally only needed by advanced
304      * tools that update existing "Class" objects in-place and need
305      * to make the Introspector re-analyze existing Class objects.
306      */

307
308     public static void flushCaches() {
309     beanInfoCache.clear();
310     declaredMethodCache.clear();
311     }
312
313     /**
314      * Flush the Introspector's internal cached information for a given class.
315      * This method is not normally required. It is normally only needed
316      * by advanced tools that update existing "Class" objects in-place
317      * and need to make the Introspector re-analyze an existing Class object.
318      *
319      * Note that only the direct state associated with the target Class
320      * object is flushed. We do not flush state for other Class objects
321      * with the same name, nor do we flush state for any related Class
322      * objects (such as subclasses), even though their state may include
323      * information indirectly obtained from the target Class object.
324      *
325      * @param clz Class object to be flushed.
326      * @throws NullPointerException If the Class object is null.
327      */

328     public static void flushFromCaches(Class JavaDoc<?> clz) {
329     if (clz == null) {
330         throw new NullPointerException JavaDoc();
331     }
332     beanInfoCache.remove(clz);
333     declaredMethodCache.remove(clz);
334     }
335
336     //======================================================================
337
// Private implementation methods
338
//======================================================================
339

340     private Introspector(Class JavaDoc beanClass, Class JavaDoc stopClass, int flags)
341                         throws IntrospectionException JavaDoc {
342     this.beanClass = beanClass;
343
344     // Check stopClass is a superClass of startClass.
345
if (stopClass != null) {
346         boolean isSuper = false;
347         for (Class JavaDoc c = beanClass.getSuperclass(); c != null; c = c.getSuperclass()) {
348             if (c == stopClass) {
349             isSuper = true;
350             }
351         }
352         if (!isSuper) {
353             throw new IntrospectionException JavaDoc(stopClass.getName() + " not superclass of " +
354                     beanClass.getName());
355         }
356     }
357
358         if (flags == USE_ALL_BEANINFO) {
359         explicitBeanInfo = findExplicitBeanInfo(beanClass);
360         }
361
362     Class JavaDoc superClass = beanClass.getSuperclass();
363     if (superClass != stopClass) {
364         int newFlags = flags;
365         if (newFlags == IGNORE_IMMEDIATE_BEANINFO) {
366         newFlags = USE_ALL_BEANINFO;
367         }
368         superBeanInfo = getBeanInfo(superClass, stopClass, newFlags);
369     }
370     if (explicitBeanInfo != null) {
371         additionalBeanInfo = explicitBeanInfo.getAdditionalBeanInfo();
372     }
373     if (additionalBeanInfo == null) {
374         additionalBeanInfo = new BeanInfo JavaDoc[0];
375     }
376     }
377
378     /**
379      * Constructs a GenericBeanInfo class from the state of the Introspector
380      */

381     private BeanInfo JavaDoc getBeanInfo() throws IntrospectionException JavaDoc {
382
383     // the evaluation order here is import, as we evaluate the
384
// event sets and locate PropertyChangeListeners before we
385
// look for properties.
386
BeanDescriptor JavaDoc bd = getTargetBeanDescriptor();
387     MethodDescriptor JavaDoc mds[] = getTargetMethodInfo();
388     EventSetDescriptor JavaDoc esds[] = getTargetEventInfo();
389     PropertyDescriptor JavaDoc pds[] = getTargetPropertyInfo();
390
391     int defaultEvent = getTargetDefaultEventIndex();
392     int defaultProperty = getTargetDefaultPropertyIndex();
393
394         return new GenericBeanInfo(bd, esds, defaultEvent, pds,
395             defaultProperty, mds, explicitBeanInfo);
396     
397     }
398
399     /**
400      * Looks for an explicit BeanInfo class that corresponds to the Class.
401      * First it looks in the existing package that the Class is defined in,
402      * then it checks to see if the class is its own BeanInfo. Finally,
403      * the BeanInfo search path is prepended to the class and searched.
404      *
405      * @return Instance of an explicit BeanInfo class or null if one isn't found.
406      */

407     private static synchronized BeanInfo JavaDoc findExplicitBeanInfo(Class JavaDoc beanClass) {
408     String JavaDoc name = beanClass.getName() + BEANINFO_SUFFIX;
409         try {
410         return (java.beans.BeanInfo JavaDoc)instantiate(beanClass, name);
411     } catch (Exception JavaDoc ex) {
412         // Just drop through
413

414         }
415     // Now try checking if the bean is its own BeanInfo.
416
try {
417         if (isSubclass(beanClass, java.beans.BeanInfo JavaDoc.class)) {
418             return (java.beans.BeanInfo JavaDoc)beanClass.newInstance();
419         }
420     } catch (Exception JavaDoc ex) {
421         // Just drop through
422
}
423     // Now try looking for <searchPath>.fooBeanInfo
424
name = name.substring(name.lastIndexOf('.')+1);
425
426     for (int i = 0; i < searchPath.length; i++) {
427         // This optimization will only use the BeanInfo search path if is has changed
428
// from the original or trying to get the ComponentBeanInfo.
429
if (!DEFAULT_INFO_PATH.equals(searchPath[i]) ||
430         DEFAULT_INFO_PATH.equals(searchPath[i]) && "ComponentBeanInfo".equals(name)) {
431         try {
432             String JavaDoc fullName = searchPath[i] + "." + name;
433             java.beans.BeanInfo JavaDoc bi = (java.beans.BeanInfo JavaDoc)instantiate(beanClass, fullName);
434
435             // Make sure that the returned BeanInfo matches the class.
436
if (bi.getBeanDescriptor() != null) {
437             if (bi.getBeanDescriptor().getBeanClass() == beanClass) {
438                 return bi;
439             }
440             } else if (bi.getPropertyDescriptors() != null) {
441             PropertyDescriptor JavaDoc[] pds = bi.getPropertyDescriptors();
442             for (int j = 0; j < pds.length; j++) {
443                 Method JavaDoc method = pds[j].getReadMethod();
444                 if (method == null) {
445                 method = pds[j].getWriteMethod();
446                 }
447                 if (method != null && method.getDeclaringClass() == beanClass) {
448                 return bi;
449                 }
450             }
451             } else if (bi.getMethodDescriptors() != null) {
452             MethodDescriptor JavaDoc[] mds = bi.getMethodDescriptors();
453             for (int j = 0; j < mds.length; j++) {
454                 Method JavaDoc method = mds[j].getMethod();
455                 if (method != null && method.getDeclaringClass() == beanClass) {
456                 return bi;
457                 }
458             }
459             }
460         } catch (Exception JavaDoc ex) {
461             // Silently ignore any errors.
462
}
463         }
464     }
465     return null;
466     }
467
468     /**
469      * @return An array of PropertyDescriptors describing the editable
470      * properties supported by the target bean.
471      */

472
473     private PropertyDescriptor JavaDoc[] getTargetPropertyInfo() {
474
475     // Check if the bean has its own BeanInfo that will provide
476
// explicit information.
477
PropertyDescriptor JavaDoc[] explicitProperties = null;
478     if (explicitBeanInfo != null) {
479         explicitProperties = explicitBeanInfo.getPropertyDescriptors();
480         int ix = explicitBeanInfo.getDefaultPropertyIndex();
481         if (ix >= 0 && ix < explicitProperties.length) {
482         defaultPropertyName = explicitProperties[ix].getName();
483         }
484         }
485
486     if (explicitProperties == null && superBeanInfo != null) {
487         // We have no explicit BeanInfo properties. Check with our parent.
488
PropertyDescriptor JavaDoc supers[] = superBeanInfo.getPropertyDescriptors();
489         for (int i = 0 ; i < supers.length; i++) {
490         addPropertyDescriptor(supers[i]);
491         }
492         int ix = superBeanInfo.getDefaultPropertyIndex();
493         if (ix >= 0 && ix < supers.length) {
494         defaultPropertyName = supers[ix].getName();
495         }
496     }
497
498     for (int i = 0; i < additionalBeanInfo.length; i++) {
499         PropertyDescriptor JavaDoc additional[] = additionalBeanInfo[i].getPropertyDescriptors();
500         if (additional != null) {
501             for (int j = 0 ; j < additional.length; j++) {
502             addPropertyDescriptor(additional[j]);
503             }
504         }
505     }
506
507     if (explicitProperties != null) {
508         // Add the explicit BeanInfo data to our results.
509
for (int i = 0 ; i < explicitProperties.length; i++) {
510         addPropertyDescriptor(explicitProperties[i]);
511         }
512
513     } else {
514
515         // Apply some reflection to the current class.
516

517         // First get an array of all the public methods at this level
518
Method JavaDoc methodList[] = getPublicDeclaredMethods(beanClass);
519
520         // Now analyze each method.
521
for (int i = 0; i < methodList.length; i++) {
522             Method JavaDoc method = methodList[i];
523         if (method == null) {
524             continue;
525         }
526             // skip static methods.
527
int mods = method.getModifiers();
528         if (Modifier.isStatic(mods)) {
529             continue;
530         }
531             String JavaDoc name = method.getName();
532             Class JavaDoc argTypes[] = method.getParameterTypes();
533             Class JavaDoc resultType = method.getReturnType();
534         int argCount = argTypes.length;
535         PropertyDescriptor JavaDoc pd = null;
536
537         if (name.length() <= 3 && !name.startsWith(IS_PREFIX)) {
538             // Optimization. Don't bother with invalid propertyNames.
539
continue;
540         }
541
542         try {
543
544                 if (argCount == 0) {
545                 if (name.startsWith(GET_PREFIX)) {
546                     // Simple getter
547
pd = new PropertyDescriptor JavaDoc(decapitalize(name.substring(3)),
548                         method, null);
549                     } else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
550                     // Boolean getter
551
pd = new PropertyDescriptor JavaDoc(decapitalize(name.substring(2)),
552                         method, null);
553                 }
554                 } else if (argCount == 1) {
555                 if (argTypes[0] == int.class && name.startsWith(GET_PREFIX)) {
556                     pd = new IndexedPropertyDescriptor JavaDoc(
557                         decapitalize(name.substring(3)),
558                         null, null,
559                         method, null);
560                 } else if (resultType == void.class && name.startsWith(SET_PREFIX)) {
561                     // Simple setter
562
pd = new PropertyDescriptor JavaDoc(decapitalize(name.substring(3)),
563                         null, method);
564                     if (throwsException(method, PropertyVetoException JavaDoc.class)) {
565                     pd.setConstrained(true);
566                 }
567                 }
568                 } else if (argCount == 2) {
569                 if (argTypes[0] == int.class && name.startsWith(SET_PREFIX)) {
570                         pd = new IndexedPropertyDescriptor JavaDoc(
571                         decapitalize(name.substring(3)),
572                         null, null,
573                         null, method);
574                     if (throwsException(method, PropertyVetoException JavaDoc.class)) {
575                     pd.setConstrained(true);
576                 }
577             }
578             }
579         } catch (IntrospectionException JavaDoc ex) {
580             // This happens if a PropertyDescriptor or IndexedPropertyDescriptor
581
// constructor fins that the method violates details of the deisgn
582
// pattern, e.g. by having an empty name, or a getter returning
583
// void , or whatever.
584
pd = null;
585         }
586
587         if (pd != null) {
588             // If this class or one of its base classes is a PropertyChange
589
// source, then we assume that any properties we discover are "bound".
590
if (propertyChangeSource) {
591             pd.setBound(true);
592             }
593             addPropertyDescriptor(pd);
594         }
595         }
596     }
597     processPropertyDescriptors();
598
599     // Allocate and populate the result array.
600
PropertyDescriptor JavaDoc result[] = new PropertyDescriptor JavaDoc[properties.size()];
601     result = (PropertyDescriptor JavaDoc[])properties.values().toArray(result);
602
603     // Set the default index.
604
if (defaultPropertyName != null) {
605         for (int i = 0; i < result.length; i++) {
606         if (defaultPropertyName.equals(result[i].getName())) {
607             defaultPropertyIndex = i;
608         }
609         }
610     }
611
612     return result;
613     }
614
615     private HashMap JavaDoc pdStore = new HashMap JavaDoc();
616
617     /**
618      * Adds the property descriptor to the list store.
619      */

620     private void addPropertyDescriptor(PropertyDescriptor JavaDoc pd) {
621     String JavaDoc propName = pd.getName();
622     List JavaDoc list = (List JavaDoc)pdStore.get(propName);
623     if (list == null) {
624         list = new ArrayList JavaDoc();
625         pdStore.put(propName, list);
626     }
627     list.add(pd);
628     }
629
630     /**
631      * Populates the property descriptor table by merging the
632      * lists of Property descriptors.
633      */

634     private void processPropertyDescriptors() {
635     if (properties == null) {
636         properties = new TreeMap JavaDoc();
637     }
638
639     List JavaDoc list;
640
641     PropertyDescriptor JavaDoc pd, gpd, spd;
642     IndexedPropertyDescriptor JavaDoc ipd, igpd, ispd;
643
644     Iterator JavaDoc it = pdStore.values().iterator();
645     while (it.hasNext()) {
646         pd = null; gpd = null; spd = null;
647         ipd = null; igpd = null; ispd = null;
648
649         list = (List JavaDoc)it.next();
650
651         // First pass. Find the latest getter method. Merge properties
652
// of previous getter methods.
653
for (int i = 0; i < list.size(); i++) {
654         pd = (PropertyDescriptor JavaDoc)list.get(i);
655         if (pd instanceof IndexedPropertyDescriptor JavaDoc) {
656             ipd = (IndexedPropertyDescriptor JavaDoc)pd;
657             if (ipd.getIndexedReadMethod() != null) {
658             if (igpd != null) {
659                 igpd = new IndexedPropertyDescriptor JavaDoc(igpd, ipd);
660             } else {
661                 igpd = ipd;
662             }
663             }
664         } else {
665             if (pd.getReadMethod() != null) {
666             if (gpd != null) {
667                 // Don't replace the existing read
668
// method if it starts with "is"
669
Method JavaDoc method = gpd.getReadMethod();
670                 if (!method.getName().startsWith(IS_PREFIX)) {
671                 gpd = new PropertyDescriptor JavaDoc(gpd, pd);
672                 }
673             } else {
674                 gpd = pd;
675             }
676             }
677         }
678         }
679
680         // Second pass. Find the latest setter method which
681
// has the same type as the getter method.
682
for (int i = 0; i < list.size(); i++) {
683         pd = (PropertyDescriptor JavaDoc)list.get(i);
684         if (pd instanceof IndexedPropertyDescriptor JavaDoc) {
685             ipd = (IndexedPropertyDescriptor JavaDoc)pd;
686             if (ipd.getIndexedWriteMethod() != null) {
687             if (igpd != null) {
688                 if (igpd.getIndexedPropertyType()
689                 == ipd.getIndexedPropertyType()) {
690                 if (ispd != null) {
691                     ispd = new IndexedPropertyDescriptor JavaDoc(ispd, ipd);
692                 } else {
693                     ispd = ipd;
694                 }
695                 }
696             } else {
697                 if (ispd != null) {
698                 ispd = new IndexedPropertyDescriptor JavaDoc(ispd, ipd);
699                 } else {
700                 ispd = ipd;
701                 }
702             }
703             }
704         } else {
705             if (pd.getWriteMethod() != null) {
706             if (gpd != null) {
707                 if (gpd.getPropertyType() == pd.getPropertyType()) {
708                 if (spd != null) {
709                     spd = new PropertyDescriptor JavaDoc(spd, pd);
710                 } else {
711                     spd = pd;
712                 }
713                 }
714             } else {
715                 if (spd != null) {
716                 spd = new PropertyDescriptor JavaDoc(spd, pd);
717                 } else {
718                 spd = pd;
719                 }
720             }
721             }
722         }
723         }
724
725         // At this stage we should have either PDs or IPDs for the
726
// representative getters and setters. The order at which the
727
// property descriptors are determined represent the
728
// precedence of the property ordering.
729
pd = null; ipd = null;
730
731         if (igpd != null && ispd != null) {
732         // Complete indexed properties set
733
// Merge any classic property descriptors
734
if (gpd != null) {
735             PropertyDescriptor JavaDoc tpd = mergePropertyDescriptor(igpd, gpd);
736             if (tpd instanceof IndexedPropertyDescriptor JavaDoc) {
737             igpd = (IndexedPropertyDescriptor JavaDoc)tpd;
738             }
739         }
740         if (spd != null) {
741             PropertyDescriptor JavaDoc tpd = mergePropertyDescriptor(ispd, spd);
742             if (tpd instanceof IndexedPropertyDescriptor JavaDoc) {
743             ispd = (IndexedPropertyDescriptor JavaDoc)tpd;
744             }
745         }
746         if (igpd == ispd) {
747             pd = igpd;
748         } else {
749             pd = mergePropertyDescriptor(igpd, ispd);
750         }
751         } else if (gpd != null && spd != null) {
752         // Complete simple properties set
753
if (gpd == spd) {
754             pd = gpd;
755         } else {
756             pd = mergePropertyDescriptor(gpd, spd);
757         }
758         } else if (ispd != null) {
759         // indexed setter
760
pd = ispd;
761         // Merge any classic property descriptors
762
if (spd != null) {
763             pd = mergePropertyDescriptor(ispd, spd);
764         }
765         if (gpd != null) {
766             pd = mergePropertyDescriptor(ispd, gpd);
767         }
768         } else if (igpd != null) {
769         // indexed getter
770
pd = igpd;
771         // Merge any classic property descriptors
772
if (gpd != null) {
773             pd = mergePropertyDescriptor(igpd, gpd);
774         }
775         if (spd != null) {
776             pd = mergePropertyDescriptor(igpd, spd);
777         }
778         } else if (spd != null) {
779         // simple setter
780
pd = spd;
781         } else if (gpd != null) {
782         // simple getter
783
pd = gpd;
784         }
785
786         // Very special case to ensure that an IndexedPropertyDescriptor
787
// doesn't contain less information than the enclosed
788
// PropertyDescriptor. If it does, then recreate as a
789
// PropertyDescriptor. See 4168833
790
if (pd instanceof IndexedPropertyDescriptor JavaDoc) {
791         ipd = (IndexedPropertyDescriptor JavaDoc)pd;
792         if (ipd.getIndexedReadMethod() == null && ipd.getIndexedWriteMethod() == null) {
793             pd = new PropertyDescriptor