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