blob: 6a50cbe7f7d1a39fcae18897a214e79af2324c4b [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package java.beans;
27
28import com.sun.beans.finder.ClassFinder;
29
30import java.lang.ref.Reference;
31import java.lang.ref.SoftReference;
32
33import java.lang.reflect.Method;
34import java.lang.reflect.Modifier;
35
36import java.security.AccessController;
37import java.security.PrivilegedAction;
38
39import java.util.Collections;
40import java.util.Map;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.Iterator;
44import java.util.EventListener;
45import java.util.List;
46import java.util.WeakHashMap;
47import java.util.TreeMap;
48import sun.reflect.misc.ReflectUtil;
49
50/**
51 * The Introspector class provides a standard way for tools to learn about
52 * the properties, events, and methods supported by a target Java Bean.
53 * <p>
54 * For each of those three kinds of information, the Introspector will
55 * separately analyze the bean's class and superclasses looking for
56 * either explicit or implicit information and use that information to
57 * build a BeanInfo object that comprehensively describes the target bean.
58 * <p>
59 * For each class "Foo", explicit information may be available if there exists
60 * a corresponding "FooBeanInfo" class that provides a non-null value when
61 * queried for the information. We first look for the BeanInfo class by
62 * taking the full package-qualified name of the target bean class and
63 * appending "BeanInfo" to form a new class name. If this fails, then
64 * we take the final classname component of this name, and look for that
65 * class in each of the packages specified in the BeanInfo package search
66 * path.
67 * <p>
68 * Thus for a class such as "sun.xyz.OurButton" we would first look for a
69 * BeanInfo class called "sun.xyz.OurButtonBeanInfo" and if that failed we'd
70 * look in each package in the BeanInfo search path for an OurButtonBeanInfo
71 * class. With the default search path, this would mean looking for
72 * "sun.beans.infos.OurButtonBeanInfo".
73 * <p>
74 * If a class provides explicit BeanInfo about itself then we add that to
75 * the BeanInfo information we obtained from analyzing any derived classes,
76 * but we regard the explicit information as being definitive for the current
77 * class and its base classes, and do not proceed any further up the superclass
78 * chain.
79 * <p>
80 * If we don't find explicit BeanInfo on a class, we use low-level
81 * reflection to study the methods of the class and apply standard design
82 * patterns to identify property accessors, event sources, or public
83 * methods. We then proceed to analyze the class's superclass and add
84 * in the information from it (and possibly on up the superclass chain).
85 *
86 * <p>
87 * Because the Introspector caches BeanInfo classes for better performance,
88 * take care if you use it in an application that uses
89 * multiple class loaders.
90 * In general, when you destroy a <code>ClassLoader</code>
91 * that has been used to introspect classes,
92 * you should use the
93 * {@link #flushCaches <code>Introspector.flushCaches</code>}
94 * or
95 * {@link #flushFromCaches <code>Introspector.flushFromCaches</code>} method
96 * to flush all of the introspected classes out of the cache.
97 *
98 * <P>
99 * For more information about introspection and design patterns, please
100 * consult the
101 * <a href="http://java.sun.com/products/javabeans/docs/index.html">JavaBeans specification</a>.
102 */
103
104public class Introspector {
105
106 // Flags that can be used to control getBeanInfo:
107 public final static int USE_ALL_BEANINFO = 1;
108 public final static int IGNORE_IMMEDIATE_BEANINFO = 2;
109 public final static int IGNORE_ALL_BEANINFO = 3;
110
111 // Static Caches to speed up introspection.
112 private static Map declaredMethodCache =
113 Collections.synchronizedMap(new WeakHashMap());
114 private static Map beanInfoCache =
115 Collections.synchronizedMap(new WeakHashMap());
116
117 private Class beanClass;
118 private BeanInfo explicitBeanInfo;
119 private BeanInfo superBeanInfo;
120 private BeanInfo additionalBeanInfo[];
121
122 private boolean propertyChangeSource = false;
123 private static Class eventListenerType = EventListener.class;
124
125 // These should be removed.
126 private String defaultEventName;
127 private String defaultPropertyName;
128 private int defaultEventIndex = -1;
129 private int defaultPropertyIndex = -1;
130
131 // Methods maps from Method objects to MethodDescriptors
132 private Map methods;
133
134 // properties maps from String names to PropertyDescriptors
135 private Map properties;
136
137 // events maps from String names to EventSetDescriptors
138 private Map events;
139
140 private final static String DEFAULT_INFO_PATH = "sun.beans.infos";
141
142 private static String[] searchPath = { DEFAULT_INFO_PATH };
143
144 private final static EventSetDescriptor[] EMPTY_EVENTSETDESCRIPTORS = new EventSetDescriptor[0];
145
146 static final String ADD_PREFIX = "add";
147 static final String REMOVE_PREFIX = "remove";
148 static final String GET_PREFIX = "get";
149 static final String SET_PREFIX = "set";
150 static final String IS_PREFIX = "is";
151
152 private static final String BEANINFO_SUFFIX = "BeanInfo";
153
154 //======================================================================
155 // Public methods
156 //======================================================================
157
158 /**
159 * Introspect on a Java Bean and learn about all its properties, exposed
160 * methods, and events.
161 * <p>
162 * If the BeanInfo class for a Java Bean has been previously Introspected
163 * then the BeanInfo class is retrieved from the BeanInfo cache.
164 *
165 * @param beanClass The bean class to be analyzed.
166 * @return A BeanInfo object describing the target bean.
167 * @exception IntrospectionException if an exception occurs during
168 * introspection.
169 * @see #flushCaches
170 * @see #flushFromCaches
171 */
172 public static BeanInfo getBeanInfo(Class<?> beanClass)
173 throws IntrospectionException
174 {
175 if (!ReflectUtil.isPackageAccessible(beanClass)) {
176 return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
177 }
178 BeanInfo bi = (BeanInfo)beanInfoCache.get(beanClass);
179 if (bi == null) {
180 bi = (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
181 beanInfoCache.put(beanClass, bi);
182 }
183 return bi;
184 }
185
186 /**
187 * Introspect on a Java bean and learn about all its properties, exposed
188 * methods, and events, subject to some control flags.
189 * <p>
190 * If the BeanInfo class for a Java Bean has been previously Introspected
191 * based on the same arguments then the BeanInfo class is retrieved
192 * from the BeanInfo cache.
193 *
194 * @param beanClass The bean class to be analyzed.
195 * @param flags Flags to control the introspection.
196 * If flags == USE_ALL_BEANINFO then we use all of the BeanInfo
197 * classes we can discover.
198 * If flags == IGNORE_IMMEDIATE_BEANINFO then we ignore any
199 * BeanInfo associated with the specified beanClass.
200 * If flags == IGNORE_ALL_BEANINFO then we ignore all BeanInfo
201 * associated with the specified beanClass or any of its
202 * parent classes.
203 * @return A BeanInfo object describing the target bean.
204 * @exception IntrospectionException if an exception occurs during
205 * introspection.
206 */
207 public static BeanInfo getBeanInfo(Class<?> beanClass, int flags)
208 throws IntrospectionException {
209 return getBeanInfo(beanClass, null, flags);
210 }
211
212 /**
213 * Introspect on a Java bean and learn all about its properties, exposed
214 * methods, below a given "stop" point.
215 * <p>
216 * If the BeanInfo class for a Java Bean has been previously Introspected
217 * based on the same arguments, then the BeanInfo class is retrieved
218 * from the BeanInfo cache.
219 *
220 * @param beanClass The bean class to be analyzed.
221 * @param stopClass The baseclass at which to stop the analysis. Any
222 * methods/properties/events in the stopClass or in its baseclasses
223 * will be ignored in the analysis.
224 * @exception IntrospectionException if an exception occurs during
225 * introspection.
226 */
227 public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)
228 throws IntrospectionException {
229 return getBeanInfo(beanClass, stopClass, USE_ALL_BEANINFO);
230 }
231
232 /**
233 * Introspect on a Java Bean and learn about all its properties,
234 * exposed methods and events, below a given {@code stopClass} point
235 * subject to some control {@code flags}.
236 * <dl>
237 * <dt>USE_ALL_BEANINFO</dt>
238 * <dd>Any BeanInfo that can be discovered will be used.</dd>
239 * <dt>IGNORE_IMMEDIATE_BEANINFO</dt>
240 * <dd>Any BeanInfo associated with the specified {@code beanClass} will be ignored.</dd>
241 * <dt>IGNORE_ALL_BEANINFO</dt>
242 * <dd>Any BeanInfo associated with the specified {@code beanClass}
243 * or any of its parent classes will be ignored.</dd>
244 * </dl>
245 * Any methods/properties/events in the {@code stopClass}
246 * or in its parent classes will be ignored in the analysis.
247 * <p>
248 * If the BeanInfo class for a Java Bean has been
249 * previously introspected based on the same arguments then
250 * the BeanInfo class is retrieved from the BeanInfo cache.
251 *
252 * @param beanClass the bean class to be analyzed
253 * @param stopClass the parent class at which to stop the analysis
254 * @param flags flags to control the introspection
255 * @return a BeanInfo object describing the target bean
256 * @exception IntrospectionException if an exception occurs during introspection
257 *
258 * @since 1.7
259 */
260 public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass,
261 int flags) throws IntrospectionException {
262 BeanInfo bi;
263 if (stopClass == null && flags == USE_ALL_BEANINFO) {
264 // Same parameters to take advantage of caching.
265 bi = getBeanInfo(beanClass);
266 } else {
267 bi = (new Introspector(beanClass, stopClass, flags)).getBeanInfo();
268 }
269 return bi;
270
271 // Old behaviour: Make an independent copy of the BeanInfo.
272 //return new GenericBeanInfo(bi);
273 }
274
275
276 /**
277 * Utility method to take a string and convert it to normal Java variable
278 * name capitalization. This normally means converting the first
279 * character from upper case to lower case, but in the (unusual) special
280 * case when there is more than one character and both the first and
281 * second characters are upper case, we leave it alone.
282 * <p>
283 * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
284 * as "URL".
285 *
286 * @param name The string to be decapitalized.
287 * @return The decapitalized version of the string.
288 */
289 public static String decapitalize(String name) {
290 if (name == null || name.length() == 0) {
291 return name;
292 }
293 if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
294 Character.isUpperCase(name.charAt(0))){
295 return name;
296 }
297 char chars[] = name.toCharArray();
298 chars[0] = Character.toLowerCase(chars[0]);
299 return new String(chars);
300 }
301
302 /**
303 * Gets the list of package names that will be used for
304 * finding BeanInfo classes.
305 *
306 * @return The array of package names that will be searched in
307 * order to find BeanInfo classes. The default value
308 * for this array is implementation-dependent; e.g.
309 * Sun implementation initially sets to {"sun.beans.infos"}.
310 */
311
312 public static synchronized String[] getBeanInfoSearchPath() {
313 // Return a copy of the searchPath.
314 String result[] = new String[searchPath.length];
315 for (int i = 0; i < searchPath.length; i++) {
316 result[i] = searchPath[i];
317 }
318 return result;
319 }
320
321 /**
322 * Change the list of package names that will be used for
323 * finding BeanInfo classes. The behaviour of
324 * this method is undefined if parameter path
325 * is null.
326 *
327 * <p>First, if there is a security manager, its <code>checkPropertiesAccess</code>
328 * method is called. This could result in a SecurityException.
329 *
330 * @param path Array of package names.
331 * @exception SecurityException if a security manager exists and its
332 * <code>checkPropertiesAccess</code> method doesn't allow setting
333 * of system properties.
334 * @see SecurityManager#checkPropertiesAccess
335 */
336
337 public static synchronized void setBeanInfoSearchPath(String path[]) {
338 SecurityManager sm = System.getSecurityManager();
339 if (sm != null) {
340 sm.checkPropertiesAccess();
341 }
342 searchPath = path;
343 }
344
345
346 /**
347 * Flush all of the Introspector's internal caches. This method is
348 * not normally required. It is normally only needed by advanced
349 * tools that update existing "Class" objects in-place and need
350 * to make the Introspector re-analyze existing Class objects.
351 */
352
353 public static void flushCaches() {
354 beanInfoCache.clear();
355 declaredMethodCache.clear();
356 }
357
358 /**
359 * Flush the Introspector's internal cached information for a given class.
360 * This method is not normally required. It is normally only needed
361 * by advanced tools that update existing "Class" objects in-place
362 * and need to make the Introspector re-analyze an existing Class object.
363 *
364 * Note that only the direct state associated with the target Class
365 * object is flushed. We do not flush state for other Class objects
366 * with the same name, nor do we flush state for any related Class
367 * objects (such as subclasses), even though their state may include
368 * information indirectly obtained from the target Class object.
369 *
370 * @param clz Class object to be flushed.
371 * @throws NullPointerException If the Class object is null.
372 */
373 public static void flushFromCaches(Class<?> clz) {
374 if (clz == null) {
375 throw new NullPointerException();
376 }
377 beanInfoCache.remove(clz);
378 declaredMethodCache.remove(clz);
379 }
380
381 //======================================================================
382 // Private implementation methods
383 //======================================================================
384
385 private Introspector(Class beanClass, Class stopClass, int flags)
386 throws IntrospectionException {
387 this.beanClass = beanClass;
388
389 // Check stopClass is a superClass of startClass.
390 if (stopClass != null) {
391 boolean isSuper = false;
392 for (Class c = beanClass.getSuperclass(); c != null; c = c.getSuperclass()) {
393 if (c == stopClass) {
394 isSuper = true;
395 }
396 }
397 if (!isSuper) {
398 throw new IntrospectionException(stopClass.getName() + " not superclass of " +
399 beanClass.getName());
400 }
401 }
402
403 if (flags == USE_ALL_BEANINFO) {
404 explicitBeanInfo = findExplicitBeanInfo(beanClass);
405 }
406
407 Class superClass = beanClass.getSuperclass();
408 if (superClass != stopClass) {
409 int newFlags = flags;
410 if (newFlags == IGNORE_IMMEDIATE_BEANINFO) {
411 newFlags = USE_ALL_BEANINFO;
412 }
413 superBeanInfo = getBeanInfo(superClass, stopClass, newFlags);
414 }
415 if (explicitBeanInfo != null) {
416 additionalBeanInfo = explicitBeanInfo.getAdditionalBeanInfo();
417 }
418 if (additionalBeanInfo == null) {
419 additionalBeanInfo = new BeanInfo[0];
420 }
421 }
422
423 /**
424 * Constructs a GenericBeanInfo class from the state of the Introspector
425 */
426 private BeanInfo getBeanInfo() throws IntrospectionException {
427
428 // the evaluation order here is import, as we evaluate the
429 // event sets and locate PropertyChangeListeners before we
430 // look for properties.
431 BeanDescriptor bd = getTargetBeanDescriptor();
432 MethodDescriptor mds[] = getTargetMethodInfo();
433 EventSetDescriptor esds[] = getTargetEventInfo();
434 PropertyDescriptor pds[] = getTargetPropertyInfo();
435
436 int defaultEvent = getTargetDefaultEventIndex();
437 int defaultProperty = getTargetDefaultPropertyIndex();
438
439 return new GenericBeanInfo(bd, esds, defaultEvent, pds,
440 defaultProperty, mds, explicitBeanInfo);
441
442 }
443
444 /**
445 * Looks for an explicit BeanInfo class that corresponds to the Class.
446 * First it looks in the existing package that the Class is defined in,
447 * then it checks to see if the class is its own BeanInfo. Finally,
448 * the BeanInfo search path is prepended to the class and searched.
449 *
450 * @return Instance of an explicit BeanInfo class or null if one isn't found.
451 */
452 private static synchronized BeanInfo findExplicitBeanInfo(Class beanClass) {
453 String name = beanClass.getName() + BEANINFO_SUFFIX;
454 try {
455 return (java.beans.BeanInfo)instantiate(beanClass, name);
456 } catch (Exception ex) {
457 // Just drop through
458
459 }
460 // Now try checking if the bean is its own BeanInfo.
461 try {
462 if (isSubclass(beanClass, java.beans.BeanInfo.class)) {
463 return (java.beans.BeanInfo)beanClass.newInstance();
464 }
465 } catch (Exception ex) {
466 // Just drop through
467 }
468 // Now try looking for <searchPath>.fooBeanInfo
469 name = name.substring(name.lastIndexOf('.')+1);
470
471 for (int i = 0; i < searchPath.length; i++) {
472 // This optimization will only use the BeanInfo search path if is has changed
473 // from the original or trying to get the ComponentBeanInfo.
474 if (!DEFAULT_INFO_PATH.equals(searchPath[i]) ||
475 DEFAULT_INFO_PATH.equals(searchPath[i]) && "ComponentBeanInfo".equals(name)) {
476 try {
477 String fullName = searchPath[i] + "." + name;
478 java.beans.BeanInfo bi = (java.beans.BeanInfo)instantiate(beanClass, fullName);
479
480 // Make sure that the returned BeanInfo matches the class.
481 if (bi.getBeanDescriptor() != null) {
482 if (bi.getBeanDescriptor().getBeanClass() == beanClass) {
483 return bi;
484 }
485 } else if (bi.getPropertyDescriptors() != null) {
486 PropertyDescriptor[] pds = bi.getPropertyDescriptors();
487 for (int j = 0; j < pds.length; j++) {
488 Method method = pds[j].getReadMethod();
489 if (method == null) {
490 method = pds[j].getWriteMethod();
491 }
492 if (method != null && method.getDeclaringClass() == beanClass) {
493 return bi;
494 }
495 }
496 } else if (bi.getMethodDescriptors() != null) {
497 MethodDescriptor[] mds = bi.getMethodDescriptors();
498 for (int j = 0; j < mds.length; j++) {
499 Method method = mds[j].getMethod();
500 if (method != null && method.getDeclaringClass() == beanClass) {
501 return bi;
502 }
503 }
504 }
505 } catch (Exception ex) {
506 // Silently ignore any errors.
507 }
508 }
509 }
510 return null;
511 }
512
513 /**
514 * @return An array of PropertyDescriptors describing the editable
515 * properties supported by the target bean.
516 */
517
518 private PropertyDescriptor[] getTargetPropertyInfo() {
519
520 // Check if the bean has its own BeanInfo that will provide
521 // explicit information.
522 PropertyDescriptor[] explicitProperties = null;
523 if (explicitBeanInfo != null) {
524 explicitProperties = getPropertyDescriptors(this.explicitBeanInfo);
525 }
526
527 if (explicitProperties == null && superBeanInfo != null) {
528 // We have no explicit BeanInfo properties. Check with our parent.
529 addPropertyDescriptors(getPropertyDescriptors(this.superBeanInfo));
530 }
531
532 for (int i = 0; i < additionalBeanInfo.length; i++) {
533 addPropertyDescriptors(additionalBeanInfo[i].getPropertyDescriptors());
534 }
535
536 if (explicitProperties != null) {
537 // Add the explicit BeanInfo data to our results.
538 addPropertyDescriptors(explicitProperties);
539
540 } else {
541
542 // Apply some reflection to the current class.
543
544 // First get an array of all the public methods at this level
545 Method methodList[] = getPublicDeclaredMethods(beanClass);
546
547 // Now analyze each method.
548 for (int i = 0; i < methodList.length; i++) {
549 Method method = methodList[i];
550 if (method == null || method.isSynthetic()) {
551 continue;
552 }
553 // skip static methods.
554 int mods = method.getModifiers();
555 if (Modifier.isStatic(mods)) {
556 continue;
557 }
558 String name = method.getName();
559 Class argTypes[] = method.getParameterTypes();
560 Class resultType = method.getReturnType();
561 int argCount = argTypes.length;
562 PropertyDescriptor pd = null;
563
564 if (name.length() <= 3 && !name.startsWith(IS_PREFIX)) {
565 // Optimization. Don't bother with invalid propertyNames.
566 continue;
567 }
568
569 try {
570
571 if (argCount == 0) {
572 if (name.startsWith(GET_PREFIX)) {
573 // Simple getter
574 pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null);
575 } else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
576 // Boolean getter
577 pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
578 }
579 } else if (argCount == 1) {
580 if (argTypes[0] == int.class && name.startsWith(GET_PREFIX)) {
581 pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
582 } else if (resultType == void.class && name.startsWith(SET_PREFIX)) {
583 // Simple setter
584 pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
585 if (throwsException(method, PropertyVetoException.class)) {
586 pd.setConstrained(true);
587 }
588 }
589 } else if (argCount == 2) {
590 if (argTypes[0] == int.class && name.startsWith(SET_PREFIX)) {
591 pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, null, method);
592 if (throwsException(method, PropertyVetoException.class)) {
593 pd.setConstrained(true);
594 }
595 }
596 }
597 } catch (IntrospectionException ex) {
598 // This happens if a PropertyDescriptor or IndexedPropertyDescriptor
599 // constructor fins that the method violates details of the deisgn
600 // pattern, e.g. by having an empty name, or a getter returning
601 // void , or whatever.
602 pd = null;
603 }
604
605 if (pd != null) {
606 // If this class or one of its base classes is a PropertyChange
607 // source, then we assume that any properties we discover are "bound".
608 if (propertyChangeSource) {
609 pd.setBound(true);
610 }
611 addPropertyDescriptor(pd);
612 }
613 }
614 }
615 processPropertyDescriptors();
616
617 // Allocate and populate the result array.
618 PropertyDescriptor result[] = new PropertyDescriptor[properties.size()];
619 result = (PropertyDescriptor[])properties.values().toArray(result);
620
621 // Set the default index.
622 if (defaultPropertyName != null) {
623 for (int i = 0; i < result.length; i++) {
624 if (defaultPropertyName.equals(result[i].getName())) {
625 defaultPropertyIndex = i;
626 }
627 }
628 }
629
630 return result;
631 }
632
633 private HashMap pdStore = new HashMap();
634
635 /**
636 * Adds the property descriptor to the list store.
637 */
638 private void addPropertyDescriptor(PropertyDescriptor pd) {
639 String propName = pd.getName();
640 List list = (List)pdStore.get(propName);
641 if (list == null) {
642 list = new ArrayList();
643 pdStore.put(propName, list);
644 }
645 if (this.beanClass != pd.getClass0()) {
646 // replace existing property descriptor
647 // only if we have types to resolve
648 // in the context of this.beanClass
649 try {
650 String name = pd.getName();
651 Method read = pd.getReadMethod();
652 Method write = pd.getWriteMethod();
653 boolean cls = true;
654 if (read != null) cls = cls && read.getGenericReturnType() instanceof Class;
655 if (write != null) cls = cls && write.getGenericParameterTypes()[0] instanceof Class;
656 if (pd instanceof IndexedPropertyDescriptor) {
657 IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor)pd;
658 Method readI = ipd.getIndexedReadMethod();
659 Method writeI = ipd.getIndexedWriteMethod();
660 if (readI != null) cls = cls && readI.getGenericReturnType() instanceof Class;
661 if (writeI != null) cls = cls && writeI.getGenericParameterTypes()[1] instanceof Class;
662 if (!cls) {
663 pd = new IndexedPropertyDescriptor(this.beanClass, name, read, write, readI, writeI);
664 }
665 } else if (!cls) {
666 pd = new PropertyDescriptor(this.beanClass, name, read, write);
667 }
668 } catch ( IntrospectionException e ) {
669 }
670 }
671 list.add(pd);
672 }
673
674 private void addPropertyDescriptors(PropertyDescriptor[] descriptors) {
675 if (descriptors != null) {
676 for (PropertyDescriptor descriptor : descriptors) {
677 addPropertyDescriptor(descriptor);
678 }
679 }
680 }
681
682 private PropertyDescriptor[] getPropertyDescriptors(BeanInfo info) {
683 PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
684 int index = info.getDefaultPropertyIndex();
685 if ((0 <= index) && (index < descriptors.length)) {
686 this.defaultPropertyName = descriptors[index].getName();
687 }
688 return descriptors;
689 }
690
691 /**
692 * Populates the property descriptor table by merging the
693 * lists of Property descriptors.
694 */
695 private void processPropertyDescriptors() {
696 if (properties == null) {
697 properties = new TreeMap();
698 }
699
700 List list;
701
702 PropertyDescriptor pd, gpd, spd;
703 IndexedPropertyDescriptor ipd, igpd, ispd;
704
705 Iterator it = pdStore.values().iterator();
706 while (it.hasNext()) {
707 pd = null; gpd = null; spd = null;
708 ipd = null; igpd = null; ispd = null;
709
710 list = (List)it.next();
711
712 // First pass. Find the latest getter method. Merge properties
713 // of previous getter methods.
714 for (int i = 0; i < list.size(); i++) {
715 pd = (PropertyDescriptor)list.get(i);
716 if (pd instanceof IndexedPropertyDescriptor) {
717 ipd = (IndexedPropertyDescriptor)pd;
718 if (ipd.getIndexedReadMethod() != null) {
719 if (igpd != null) {
720 igpd = new IndexedPropertyDescriptor(igpd, ipd);
721 } else {
722 igpd = ipd;
723 }
724 }
725 } else {
726 if (pd.getReadMethod() != null) {
727 if (gpd != null) {
728 // Don't replace the existing read
729 // method if it starts with "is"
730 Method method = gpd.getReadMethod();
731 if (!method.getName().startsWith(IS_PREFIX)) {
732 gpd = new PropertyDescriptor(gpd, pd);
733 }
734 } else {
735 gpd = pd;
736 }
737 }
738 }
739 }
740
741 // Second pass. Find the latest setter method which
742 // has the same type as the getter method.
743 for (int i = 0; i < list.size(); i++) {
744 pd = (PropertyDescriptor)list.get(i);
745 if (pd instanceof IndexedPropertyDescriptor) {
746 ipd = (IndexedPropertyDescriptor)pd;
747 if (ipd.getIndexedWriteMethod() != null) {
748 if (igpd != null) {
749 if (igpd.getIndexedPropertyType()
750 == ipd.getIndexedPropertyType()) {
751 if (ispd != null) {
752 ispd = new IndexedPropertyDescriptor(ispd, ipd);
753 } else {
754 ispd = ipd;
755 }
756 }
757 } else {
758 if (ispd != null) {
759 ispd = new IndexedPropertyDescriptor(ispd, ipd);
760 } else {
761 ispd = ipd;
762 }
763 }
764 }
765 } else {
766 if (pd.getWriteMethod() != null) {
767 if (gpd != null) {
768 if (gpd.getPropertyType() == pd.getPropertyType()) {
769 if (spd != null) {
770 spd = new PropertyDescriptor(spd, pd);
771 } else {
772 spd = pd;
773 }
774 }
775 } else {
776 if (spd != null) {
777 spd = new PropertyDescriptor(spd, pd);
778 } else {
779 spd = pd;
780 }
781 }
782 }
783 }
784 }
785
786 // At this stage we should have either PDs or IPDs for the
787 // representative getters and setters. The order at which the
788 // property descriptors are determined represent the
789 // precedence of the property ordering.
790 pd = null; ipd = null;
791
792 if (igpd != null && ispd != null) {
793 // Complete indexed properties set
794 // Merge any classic property descriptors
795 if (gpd != null) {
796 PropertyDescriptor tpd = mergePropertyDescriptor(igpd, gpd);
797 if (tpd instanceof IndexedPropertyDescriptor) {
798 igpd = (IndexedPropertyDescriptor)tpd;
799 }
800 }
801 if (spd != null) {
802 PropertyDescriptor tpd = mergePropertyDescriptor(ispd, spd);
803 if (tpd instanceof IndexedPropertyDescriptor) {
804 ispd = (IndexedPropertyDescriptor)tpd;
805 }
806 }
807 if (igpd == ispd) {
808 pd = igpd;
809 } else {
810 pd = mergePropertyDescriptor(igpd, ispd);
811 }
812 } else if (gpd != null && spd != null) {
813 // Complete simple properties set
814 if (gpd == spd) {
815 pd = gpd;
816 } else {
817 pd = mergePropertyDescriptor(gpd, spd);
818 }
819 } else if (ispd != null) {
820 // indexed setter
821 pd = ispd;
822 // Merge any classic property descriptors
823 if (spd != null) {
824 pd = mergePropertyDescriptor(ispd, spd);
825 }
826 if (gpd != null) {
827 pd = mergePropertyDescriptor(ispd, gpd);
828 }
829 } else if (igpd != null) {
830 // indexed getter
831 pd = igpd;
832 // Merge any classic property descriptors
833 if (gpd != null) {
834 pd = mergePropertyDescriptor(igpd, gpd);
835 }
836 if (spd != null) {
837 pd = mergePropertyDescriptor(igpd, spd);
838 }
839 } else if (spd != null) {
840 // simple setter
841 pd = spd;
842 } else if (gpd != null) {
843 // simple getter
844 pd = gpd;
845 }
846
847 // Very special case to ensure that an IndexedPropertyDescriptor
848 // doesn't contain less information than the enclosed
849 // PropertyDescriptor. If it does, then recreate as a
850 // PropertyDescriptor. See 4168833
851 if (pd instanceof IndexedPropertyDescriptor) {
852 ipd = (IndexedPropertyDescriptor)pd;
853 if (ipd.getIndexedReadMethod() == null && ipd.getIndexedWriteMethod() == null) {
854 pd = new PropertyDescriptor(ipd);
855 }
856 }
857
858 // Find the first property descriptor
859 // which does not have getter and setter methods.
860 // See regression bug 4984912.
861 if ( (pd == null) && (list.size() > 0) ) {
862 pd = (PropertyDescriptor) list.get(0);
863 }
864
865 if (pd != null) {
866 properties.put(pd.getName(), pd);
867 }
868 }
869 }
870
871 /**
872 * Adds the property descriptor to the indexedproperty descriptor only if the
873 * types are the same.
874 *
875 * The most specific property descriptor will take precedence.
876 */
877 private PropertyDescriptor mergePropertyDescriptor(IndexedPropertyDescriptor ipd,
878 PropertyDescriptor pd) {
879 PropertyDescriptor result = null;
880
881 Class propType = pd.getPropertyType();
882 Class ipropType = ipd.getIndexedPropertyType();
883
884 if (propType.isArray() && propType.getComponentType() == ipropType) {
885 if (pd.getClass0().isAssignableFrom(ipd.getClass0())) {
886 result = new IndexedPropertyDescriptor(pd, ipd);
887 } else {
888 result = new IndexedPropertyDescriptor(ipd, pd);
889 }
890 } else {
891 // Cannot merge the pd because of type mismatch
892 // Return the most specific pd
893 if (pd.getClass0().isAssignableFrom(ipd.getClass0())) {
894 result = ipd;
895 } else {
896 result = pd;
897 // Try to add methods which may have been lost in the type change
898 // See 4168833
899 Method write = result.getWriteMethod();
900 Method read = result.getReadMethod();
901
902 if (read == null && write != null) {
903 read = findMethod(result.getClass0(),
904 GET_PREFIX + NameGenerator.capitalize(result.getName()), 0);
905 if (read != null) {
906 try {
907 result.setReadMethod(read);
908 } catch (IntrospectionException ex) {
909 // no consequences for failure.
910 }
911 }
912 }
913 if (write == null && read != null) {
914 write = findMethod(result.getClass0(),
915 SET_PREFIX + NameGenerator.capitalize(result.getName()), 1,
916 new Class[] { FeatureDescriptor.getReturnType(result.getClass0(), read) });
917 if (write != null) {
918 try {
919 result.setWriteMethod(write);
920 } catch (IntrospectionException ex) {
921 // no consequences for failure.
922 }
923 }
924 }
925 }
926 }
927 return result;
928 }
929
930 // Handle regular pd merge
931 private PropertyDescriptor mergePropertyDescriptor(PropertyDescriptor pd1,
932 PropertyDescriptor pd2) {
933 if (pd1.getClass0().isAssignableFrom(pd2.getClass0())) {
934 return new PropertyDescriptor(pd1, pd2);
935 } else {
936 return new PropertyDescriptor(pd2, pd1);
937 }
938 }
939
940 // Handle regular ipd merge
941 private PropertyDescriptor mergePropertyDescriptor(IndexedPropertyDescriptor ipd1,
942 IndexedPropertyDescriptor ipd2) {
943 if (ipd1.getClass0().isAssignableFrom(ipd2.getClass0())) {
944 return new IndexedPropertyDescriptor(ipd1, ipd2);
945 } else {
946 return new IndexedPropertyDescriptor(ipd2, ipd1);
947 }
948 }
949
950 /**
951 * @return An array of EventSetDescriptors describing the kinds of
952 * events fired by the target bean.
953 */
954 private EventSetDescriptor[] getTargetEventInfo() throws IntrospectionException {
955 if (events == null) {
956 events = new HashMap();
957 }
958
959 // Check if the bean has its own BeanInfo that will provide
960 // explicit information.
961 EventSetDescriptor[] explicitEvents = null;
962 if (explicitBeanInfo != null) {
963 explicitEvents = explicitBeanInfo.getEventSetDescriptors();
964 int ix = explicitBeanInfo.getDefaultEventIndex();
965 if (ix >= 0 && ix < explicitEvents.length) {
966 defaultEventName = explicitEvents[ix].getName();
967 }
968 }
969
970 if (explicitEvents == null && superBeanInfo != null) {
971 // We have no explicit BeanInfo events. Check with our parent.
972 EventSetDescriptor supers[] = superBeanInfo.getEventSetDescriptors();
973 for (int i = 0 ; i < supers.length; i++) {
974 addEvent(supers[i]);
975 }
976 int ix = superBeanInfo.getDefaultEventIndex();
977 if (ix >= 0 && ix < supers.length) {
978 defaultEventName = supers[ix].getName();
979 }
980 }
981
982 for (int i = 0; i < additionalBeanInfo.length; i++) {
983 EventSetDescriptor additional[] = additionalBeanInfo[i].getEventSetDescriptors();
984 if (additional != null) {
985 for (int j = 0 ; j < additional.length; j++) {
986 addEvent(additional[j]);
987 }
988 }
989 }
990
991 if (explicitEvents != null) {
992 // Add the explicit explicitBeanInfo data to our results.
993 for (int i = 0 ; i < explicitEvents.length; i++) {
994 addEvent(explicitEvents[i]);
995 }
996
997 } else {
998
999 // Apply some reflection to the current class.
1000
1001 // Get an array of all the public beans methods at this level
1002 Method methodList[] = getPublicDeclaredMethods(beanClass);
1003
1004 // Find all suitable "add", "remove" and "get" Listener methods
1005 // The name of the listener type is the key for these hashtables
1006 // i.e, ActionListener
1007 Map adds = null;
1008 Map removes = null;
1009 Map gets = null;
1010
1011 for (int i = 0; i < methodList.length; i++) {
1012 Method method = methodList[i];
1013 if (method == null) {
1014 continue;
1015 }
1016 // skip static methods.
1017 int mods = method.getModifiers();
1018 if (Modifier.isStatic(mods)) {
1019 continue;
1020 }
1021 String name = method.getName();
1022 // Optimization avoid getParameterTypes
1023 if (!name.startsWith(ADD_PREFIX) && !name.startsWith(REMOVE_PREFIX)
1024 && !name.startsWith(GET_PREFIX)) {
1025 continue;
1026 }
1027
1028 Class argTypes[] = FeatureDescriptor.getParameterTypes(beanClass, method);
1029 Class resultType = FeatureDescriptor.getReturnType(beanClass, method);
1030
1031 if (name.startsWith(ADD_PREFIX) && argTypes.length == 1 &&
1032 resultType == Void.TYPE &&
1033 Introspector.isSubclass(argTypes[0], eventListenerType)) {
1034 String listenerName = name.substring(3);
1035 if (listenerName.length() > 0 &&
1036 argTypes[0].getName().endsWith(listenerName)) {
1037 if (adds == null) {
1038 adds = new HashMap();
1039 }
1040 adds.put(listenerName, method);
1041 }
1042 }
1043 else if (name.startsWith(REMOVE_PREFIX) && argTypes.length == 1 &&
1044 resultType == Void.TYPE &&
1045 Introspector.isSubclass(argTypes[0], eventListenerType)) {
1046 String listenerName = name.substring(6);
1047 if (listenerName.length() > 0 &&
1048 argTypes[0].getName().endsWith(listenerName)) {
1049 if (removes == null) {
1050 removes = new HashMap();
1051 }
1052 removes.put(listenerName, method);
1053 }
1054 }
1055 else if (name.startsWith(GET_PREFIX) && argTypes.length == 0 &&
1056 resultType.isArray() &&
1057 Introspector.isSubclass(resultType.getComponentType(),
1058 eventListenerType)) {
1059 String listenerName = name.substring(3, name.length() - 1);
1060 if (listenerName.length() > 0 &&
1061 resultType.getComponentType().getName().endsWith(listenerName)) {
1062 if (gets == null) {
1063 gets = new HashMap();
1064 }
1065 gets.put(listenerName, method);
1066 }
1067 }
1068 }
1069
1070 if (adds != null && removes != null) {
1071 // Now look for matching addFooListener+removeFooListener pairs.
1072 // Bonus if there is a matching getFooListeners method as well.
1073 Iterator keys = adds.keySet().iterator();
1074 while (keys.hasNext()) {
1075 String listenerName = (String) keys.next();
1076 // Skip any "add" which doesn't have a matching "remove" or
1077 // a listener name that doesn't end with Listener
1078 if (removes.get(listenerName) == null || !listenerName.endsWith("Listener")) {
1079 continue;
1080 }
1081 String eventName = decapitalize(listenerName.substring(0, listenerName.length()-8));
1082 Method addMethod = (Method)adds.get(listenerName);
1083 Method removeMethod = (Method)removes.get(listenerName);
1084 Method getMethod = null;
1085 if (gets != null) {
1086 getMethod = (Method)gets.get(listenerName);
1087 }
1088 Class argType = FeatureDescriptor.getParameterTypes(beanClass, addMethod)[0];
1089
1090 // generate a list of Method objects for each of the target methods:
1091 Method allMethods[] = getPublicDeclaredMethods(argType);
1092 List validMethods = new ArrayList(allMethods.length);
1093 for (int i = 0; i < allMethods.length; i++) {
1094 if (allMethods[i] == null) {
1095 continue;
1096 }
1097
1098 if (isEventHandler(allMethods[i])) {
1099 validMethods.add(allMethods[i]);
1100 }
1101 }
1102 Method[] methods = (Method[])validMethods.toArray(new Method[validMethods.size()]);
1103
1104 EventSetDescriptor esd = new EventSetDescriptor(eventName, argType,
1105 methods, addMethod,
1106 removeMethod,
1107 getMethod);
1108
1109 // If the adder method throws the TooManyListenersException then it
1110 // is a Unicast event source.
1111 if (throwsException(addMethod,
1112 java.util.TooManyListenersException.class)) {
1113 esd.setUnicast(true);
1114 }
1115 addEvent(esd);
1116 }
1117 } // if (adds != null ...
1118 }
1119 EventSetDescriptor[] result;
1120 if (events.size() == 0) {
1121 result = EMPTY_EVENTSETDESCRIPTORS;
1122 } else {
1123 // Allocate and populate the result array.
1124 result = new EventSetDescriptor[events.size()];
1125 result = (EventSetDescriptor[])events.values().toArray(result);
1126
1127 // Set the default index.
1128 if (defaultEventName != null) {
1129 for (int i = 0; i < result.length; i++) {
1130 if (defaultEventName.equals(result[i].getName())) {
1131 defaultEventIndex = i;
1132 }
1133 }
1134 }
1135 }
1136 return result;
1137 }
1138
1139 private void addEvent(EventSetDescriptor esd) {
1140 String key = esd.getName();
1141 if (esd.getName().equals("propertyChange")) {
1142 propertyChangeSource = true;
1143 }
1144 EventSetDescriptor old = (EventSetDescriptor)events.get(key);
1145 if (old == null) {
1146 events.put(key, esd);
1147 return;
1148 }
1149 EventSetDescriptor composite = new EventSetDescriptor(old, esd);
1150 events.put(key, composite);
1151 }
1152
1153 /**
1154 * @return An array of MethodDescriptors describing the private
1155 * methods supported by the target bean.
1156 */
1157 private MethodDescriptor[] getTargetMethodInfo() {
1158 if (methods == null) {
1159 methods = new HashMap(100);
1160 }
1161
1162 // Check if the bean has its own BeanInfo that will provide
1163 // explicit information.
1164 MethodDescriptor[] explicitMethods = null;
1165 if (explicitBeanInfo != null) {
1166 explicitMethods = explicitBeanInfo.getMethodDescriptors();
1167 }
1168
1169 if (explicitMethods == null && superBeanInfo != null) {
1170 // We have no explicit BeanInfo methods. Check with our parent.
1171 MethodDescriptor supers[] = superBeanInfo.getMethodDescriptors();
1172 for (int i = 0 ; i < supers.length; i++) {
1173 addMethod(supers[i]);
1174 }
1175 }
1176
1177 for (int i = 0; i < additionalBeanInfo.length; i++) {
1178 MethodDescriptor additional[] = additionalBeanInfo[i].getMethodDescriptors();
1179 if (additional != null) {
1180 for (int j = 0 ; j < additional.length; j++) {
1181 addMethod(additional[j]);
1182 }
1183 }
1184 }
1185
1186 if (explicitMethods != null) {
1187 // Add the explicit explicitBeanInfo data to our results.
1188 for (int i = 0 ; i < explicitMethods.length; i++) {
1189 addMethod(explicitMethods[i]);
1190 }
1191
1192 } else {
1193
1194 // Apply some reflection to the current class.
1195
1196 // First get an array of all the beans methods at this level
1197 Method methodList[] = getPublicDeclaredMethods(beanClass);
1198
1199 // Now analyze each method.
1200 for (int i = 0; i < methodList.length; i++) {
1201 Method method = methodList[i];
1202 if (method == null) {
1203 continue;
1204 }
1205 MethodDescriptor md = new MethodDescriptor(method);
1206 addMethod(md);
1207 }
1208 }
1209
1210 // Allocate and populate the result array.
1211 MethodDescriptor result[] = new MethodDescriptor[methods.size()];
1212 result = (MethodDescriptor[])methods.values().toArray(result);
1213
1214 return result;
1215 }
1216
1217 private void addMethod(MethodDescriptor md) {
1218 // We have to be careful here to distinguish method by both name
1219 // and argument lists.
1220 // This method gets called a *lot, so we try to be efficient.
1221 String name = md.getName();
1222
1223 MethodDescriptor old = (MethodDescriptor)methods.get(name);
1224 if (old == null) {
1225 // This is the common case.
1226 methods.put(name, md);
1227 return;
1228 }
1229
1230 // We have a collision on method names. This is rare.
1231
1232 // Check if old and md have the same type.
1233 String[] p1 = md.getParamNames();
1234 String[] p2 = old.getParamNames();
1235
1236 boolean match = false;
1237 if (p1.length == p2.length) {
1238 match = true;
1239 for (int i = 0; i < p1.length; i++) {
1240 if (p1[i] != p2[i]) {
1241 match = false;
1242 break;
1243 }
1244 }
1245 }
1246 if (match) {
1247 MethodDescriptor composite = new MethodDescriptor(old, md);
1248 methods.put(name, composite);
1249 return;
1250 }
1251
1252 // We have a collision on method names with different type signatures.
1253 // This is very rare.
1254
1255 String longKey = makeQualifiedMethodName(name, p1);
1256 old = (MethodDescriptor)methods.get(longKey);
1257 if (old == null) {
1258 methods.put(longKey, md);
1259 return;
1260 }
1261 MethodDescriptor composite = new MethodDescriptor(old, md);
1262 methods.put(longKey, composite);
1263 }
1264
1265 /**
1266 * Creates a key for a method in a method cache.
1267 */
1268 private static String makeQualifiedMethodName(String name, String[] params) {
1269 StringBuffer sb = new StringBuffer(name);
1270 sb.append('=');
1271 for (int i = 0; i < params.length; i++) {
1272 sb.append(':');
1273 sb.append(params[i]);
1274 }
1275 return sb.toString();
1276 }
1277
1278 private int getTargetDefaultEventIndex() {
1279 return defaultEventIndex;
1280 }
1281
1282 private int getTargetDefaultPropertyIndex() {
1283 return defaultPropertyIndex;
1284 }
1285
1286 private BeanDescriptor getTargetBeanDescriptor() {
1287 // Use explicit info, if available,
1288 if (explicitBeanInfo != null) {
1289 BeanDescriptor bd = explicitBeanInfo.getBeanDescriptor();
1290 if (bd != null) {
1291 return (bd);
1292 }
1293 }
1294 // OK, fabricate a default BeanDescriptor.
1295 return (new BeanDescriptor(beanClass));
1296 }
1297
1298 private boolean isEventHandler(Method m) {
1299 // We assume that a method is an event handler if it has a single
1300 // argument, whose type inherit from java.util.Event.
1301 Class argTypes[] = FeatureDescriptor.getParameterTypes(beanClass, m);
1302 if (argTypes.length != 1) {
1303 return false;
1304 }
1305 if (isSubclass(argTypes[0], java.util.EventObject.class)) {
1306 return true;
1307 }
1308 return false;
1309 }
1310
1311 /*
1312 * Internal method to return *public* methods within a class.
1313 */
1314 private static synchronized Method[] getPublicDeclaredMethods(Class clz) {
1315 // Looking up Class.getDeclaredMethods is relatively expensive,
1316 // so we cache the results.
1317 Method[] result = null;
1318 if (!ReflectUtil.isPackageAccessible(clz)) {
1319 return new Method[0];
1320 }
1321 final Class fclz = clz;
1322 Reference ref = (Reference)declaredMethodCache.get(fclz);
1323 if (ref != null) {
1324 result = (Method[])ref.get();
1325 if (result != null) {
1326 return result;
1327 }
1328 }
1329
1330 // We have to raise privilege for getDeclaredMethods
1331 result = (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
1332 public Object run() {
1333 return fclz.getDeclaredMethods();
1334 }
1335 });
1336
1337
1338 // Null out any non-public methods.
1339 for (int i = 0; i < result.length; i++) {
1340 Method method = result[i];
1341 int mods = method.getModifiers();
1342 if (!Modifier.isPublic(mods)) {
1343 result[i] = null;
1344 }
1345 }
1346 // Add it to the cache.
1347 declaredMethodCache.put(fclz, new SoftReference(result));
1348 return result;
1349 }
1350
1351 //======================================================================
1352 // Package private support methods.
1353 //======================================================================
1354
1355 /**
1356 * Internal support for finding a target methodName with a given
1357 * parameter list on a given class.
1358 */
1359 private static Method internalFindMethod(Class start, String methodName,
1360 int argCount, Class args[]) {
1361 // For overriden methods we need to find the most derived version.
1362 // So we start with the given class and walk up the superclass chain.
1363
1364 Method method = null;
1365
1366 for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
1367 Method methods[] = getPublicDeclaredMethods(cl);
1368 for (int i = 0; i < methods.length; i++) {
1369 method = methods[i];
1370 if (method == null) {
1371 continue;
1372 }
1373
1374 // make sure method signature matches.
1375 Class params[] = FeatureDescriptor.getParameterTypes(start, method);
1376 if (method.getName().equals(methodName) &&
1377 params.length == argCount) {
1378 if (args != null) {
1379 boolean different = false;
1380 if (argCount > 0) {
1381 for (int j = 0; j < argCount; j++) {
1382 if (params[j] != args[j]) {
1383 different = true;
1384 continue;
1385 }
1386 }
1387 if (different) {
1388 continue;
1389 }
1390 }
1391 }
1392 return method;
1393 }
1394 }
1395 }
1396 method = null;
1397
1398 // Now check any inherited interfaces. This is necessary both when
1399 // the argument class is itself an interface, and when the argument
1400 // class is an abstract class.
1401 Class ifcs[] = start.getInterfaces();
1402 for (int i = 0 ; i < ifcs.length; i++) {
1403 // Note: The original implementation had both methods calling
1404 // the 3 arg method. This is preserved but perhaps it should
1405 // pass the args array instead of null.
1406 method = internalFindMethod(ifcs[i], methodName, argCount, null);
1407 if (method != null) {
1408 break;
1409 }
1410 }
1411 return method;
1412 }
1413
1414 /**
1415 * Find a target methodName on a given class.
1416 */
1417 static Method findMethod(Class cls, String methodName, int argCount) {
1418 return findMethod(cls, methodName, argCount, null);
1419 }
1420
1421 /**
1422 * Find a target methodName with specific parameter list on a given class.
1423 * <p>
1424 * Used in the contructors of the EventSetDescriptor,
1425 * PropertyDescriptor and the IndexedPropertyDescriptor.
1426 * <p>
1427 * @param cls The Class object on which to retrieve the method.
1428 * @param methodName Name of the method.
1429 * @param argCount Number of arguments for the desired method.
1430 * @param args Array of argument types for the method.
1431 * @return the method or null if not found
1432 */
1433 static Method findMethod(Class cls, String methodName, int argCount,
1434 Class args[]) {
1435 if (methodName == null) {
1436 return null;
1437 }
1438 return internalFindMethod(cls, methodName, argCount, args);
1439 }
1440
1441 /**
1442 * Return true if class a is either equivalent to class b, or
1443 * if class a is a subclass of class b, i.e. if a either "extends"
1444 * or "implements" b.
1445 * Note tht either or both "Class" objects may represent interfaces.
1446 */
1447 static boolean isSubclass(Class a, Class b) {
1448 // We rely on the fact that for any given java class or
1449 // primtitive type there is a unqiue Class object, so
1450 // we can use object equivalence in the comparisons.
1451 if (a == b) {
1452 return true;
1453 }
1454 if (a == null || b == null) {
1455 return false;
1456 }
1457 for (Class x = a; x != null; x = x.getSuperclass()) {
1458 if (x == b) {
1459 return true;
1460 }
1461 if (b.isInterface()) {
1462 Class interfaces[] = x.getInterfaces();
1463 for (int i = 0; i < interfaces.length; i++) {
1464 if (isSubclass(interfaces[i], b)) {
1465 return true;
1466 }
1467 }
1468 }
1469 }
1470 return false;
1471 }
1472
1473 /**
1474 * Return true iff the given method throws the given exception.
1475 */
1476 private boolean throwsException(Method method, Class exception) {
1477 Class exs[] = method.getExceptionTypes();
1478 for (int i = 0; i < exs.length; i++) {
1479 if (exs[i] == exception) {
1480 return true;
1481 }
1482 }
1483 return false;
1484 }
1485
1486
1487 /**
1488 * Try to create an instance of a named class.
1489 * First try the classloader of "sibling", then try the system
1490 * classloader then the class loader of the current Thread.
1491 */
1492 static Object instantiate(Class sibling, String className)
1493 throws InstantiationException, IllegalAccessException,
1494 ClassNotFoundException {
1495 // First check with sibling's classloader (if any).
1496 ClassLoader cl = sibling.getClassLoader();
1497 Class cls = ClassFinder.findClass(className, cl);
1498 return cls.newInstance();
1499 }
1500
1501} // end class Introspector
1502
1503//===========================================================================
1504
1505/**
1506 * Package private implementation support class for Introspector's
1507 * internal use.
1508 * <p>
1509 * Mostly this is used as a placeholder for the descriptors.
1510 */
1511
1512class GenericBeanInfo extends SimpleBeanInfo {
1513
1514 private BeanDescriptor beanDescriptor;
1515 private EventSetDescriptor[] events;
1516 private int defaultEvent;
1517 private PropertyDescriptor[] properties;
1518 private int defaultProperty;
1519 private MethodDescriptor[] methods;
1520 private BeanInfo targetBeanInfo;
1521
1522 public GenericBeanInfo(BeanDescriptor beanDescriptor,
1523 EventSetDescriptor[] events, int defaultEvent,
1524 PropertyDescriptor[] properties, int defaultProperty,
1525 MethodDescriptor[] methods, BeanInfo targetBeanInfo) {
1526 this.beanDescriptor = beanDescriptor;
1527 this.events = events;
1528 this.defaultEvent = defaultEvent;
1529 this.properties = properties;
1530 this.defaultProperty = defaultProperty;
1531 this.methods = methods;
1532 this.targetBeanInfo = targetBeanInfo;
1533 }
1534
1535 /**
1536 * Package-private dup constructor
1537 * This must isolate the new object from any changes to the old object.
1538 */
1539 GenericBeanInfo(GenericBeanInfo old) {
1540
1541 beanDescriptor = new BeanDescriptor(old.beanDescriptor);
1542 if (old.events != null) {
1543 int len = old.events.length;
1544 events = new EventSetDescriptor[len];
1545 for (int i = 0; i < len; i++) {
1546 events[i] = new EventSetDescriptor(old.events[i]);
1547 }
1548 }
1549 defaultEvent = old.defaultEvent;
1550 if (old.properties != null) {
1551 int len = old.properties.length;
1552 properties = new PropertyDescriptor[len];
1553 for (int i = 0; i < len; i++) {
1554 PropertyDescriptor oldp = old.properties[i];
1555 if (oldp instanceof IndexedPropertyDescriptor) {
1556 properties[i] = new IndexedPropertyDescriptor(
1557 (IndexedPropertyDescriptor) oldp);
1558 } else {
1559 properties[i] = new PropertyDescriptor(oldp);
1560 }
1561 }
1562 }
1563 defaultProperty = old.defaultProperty;
1564 if (old.methods != null) {
1565 int len = old.methods.length;
1566 methods = new MethodDescriptor[len];
1567 for (int i = 0; i < len; i++) {
1568 methods[i] = new MethodDescriptor(old.methods[i]);
1569 }
1570 }
1571 targetBeanInfo = old.targetBeanInfo;
1572 }
1573
1574 public PropertyDescriptor[] getPropertyDescriptors() {
1575 return properties;
1576 }
1577
1578 public int getDefaultPropertyIndex() {
1579 return defaultProperty;
1580 }
1581
1582 public EventSetDescriptor[] getEventSetDescriptors() {
1583 return events;
1584 }
1585
1586 public int getDefaultEventIndex() {
1587 return defaultEvent;
1588 }
1589
1590 public MethodDescriptor[] getMethodDescriptors() {
1591 return methods;
1592 }
1593
1594 public BeanDescriptor getBeanDescriptor() {
1595 return beanDescriptor;
1596 }
1597
1598 public java.awt.Image getIcon(int iconKind) {
1599 if (targetBeanInfo != null) {
1600 return targetBeanInfo.getIcon(iconKind);
1601 }
1602 return super.getIcon(iconKind);
1603 }
1604}