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