Path: blob/master/src/java.desktop/share/classes/java/beans/Introspector.java
41152 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.beans;2627import java.awt.Component;28import java.lang.ref.Reference;29import java.lang.ref.SoftReference;30import java.lang.reflect.Constructor;31import java.lang.reflect.InvocationTargetException;32import java.lang.reflect.Method;33import java.lang.reflect.Type;34import java.util.ArrayList;35import java.util.EventObject;36import java.util.HashMap;37import java.util.Iterator;38import java.util.List;39import java.util.Map;40import java.util.TreeMap;4142import com.sun.beans.TypeResolver;43import com.sun.beans.finder.ClassFinder;44import com.sun.beans.introspect.ClassInfo;45import com.sun.beans.introspect.EventSetInfo;46import com.sun.beans.introspect.PropertyInfo;47import jdk.internal.access.JavaBeansAccess;48import jdk.internal.access.SharedSecrets;49import sun.reflect.misc.ReflectUtil;5051/**52* The Introspector class provides a standard way for tools to learn about53* the properties, events, and methods supported by a target Java Bean.54* <p>55* For each of those three kinds of information, the Introspector will56* separately analyze the bean's class and superclasses looking for57* either explicit or implicit information and use that information to58* build a BeanInfo object that comprehensively describes the target bean.59* <p>60* For each class "Foo", explicit information may be available if there exists61* a corresponding "FooBeanInfo" class that provides a non-null value when62* queried for the information. We first look for the BeanInfo class by63* taking the full package-qualified name of the target bean class and64* appending "BeanInfo" to form a new class name. If this fails, then65* we take the final classname component of this name, and look for that66* class in each of the packages specified in the BeanInfo package search67* path.68* <p>69* Thus for a class such as "sun.xyz.OurButton" we would first look for a70* BeanInfo class called "sun.xyz.OurButtonBeanInfo" and if that failed we'd71* look in each package in the BeanInfo search path for an OurButtonBeanInfo72* class. With the default search path, this would mean looking for73* "sun.beans.infos.OurButtonBeanInfo".74* <p>75* If a class provides explicit BeanInfo about itself then we add that to76* the BeanInfo information we obtained from analyzing any derived classes,77* but we regard the explicit information as being definitive for the current78* class and its base classes, and do not proceed any further up the superclass79* chain.80* <p>81* If we don't find explicit BeanInfo on a class, we use low-level82* reflection to study the methods of the class and apply standard design83* patterns to identify property accessors, event sources, or public84* methods. We then proceed to analyze the class's superclass and add85* in the information from it (and possibly on up the superclass chain).86* <p>87* For more information about introspection and design patterns, please88* consult the89* <a href="http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html">JavaBeans specification</a>.90*91* @since 1.192*/9394public class Introspector {9596// Flags that can be used to control getBeanInfo:97/**98* Flag to indicate to use of all beaninfo.99* @since 1.2100*/101public static final int USE_ALL_BEANINFO = 1;102/**103* Flag to indicate to ignore immediate beaninfo.104* @since 1.2105*/106public static final int IGNORE_IMMEDIATE_BEANINFO = 2;107/**108* Flag to indicate to ignore all beaninfo.109* @since 1.2110*/111public static final int IGNORE_ALL_BEANINFO = 3;112113private Class<?> beanClass;114private BeanInfo explicitBeanInfo;115private BeanInfo superBeanInfo;116private BeanInfo[] additionalBeanInfo;117118private boolean propertyChangeSource = false;119120// These should be removed.121private String defaultEventName;122private String defaultPropertyName;123private int defaultEventIndex = -1;124private int defaultPropertyIndex = -1;125126// Methods maps from Method names to MethodDescriptors127private Map<String, MethodDescriptor> methods;128129// properties maps from String names to PropertyDescriptors130private Map<String, PropertyDescriptor> properties;131132// events maps from String names to EventSetDescriptors133private Map<String, EventSetDescriptor> events;134135private static final EventSetDescriptor[] EMPTY_EVENTSETDESCRIPTORS = new EventSetDescriptor[0];136137static final String ADD_PREFIX = "add";138static final String REMOVE_PREFIX = "remove";139static final String GET_PREFIX = "get";140static final String SET_PREFIX = "set";141static final String IS_PREFIX = "is";142143// register with SharedSecrets for JMX usage144static {145SharedSecrets.setJavaBeansAccess(new JavaBeansAccess() {146@Override147public Method getReadMethod(Class<?> clazz, String property) throws Exception {148BeanInfo bi = Introspector.getBeanInfo(clazz);149PropertyDescriptor[] pds = bi.getPropertyDescriptors();150for (PropertyDescriptor pd: pds) {151if (pd.getName().equals(property)) {152return pd.getReadMethod();153}154}155return null;156}157158@Override159public String[] getConstructorPropertiesValue(Constructor<?> ctr) {160ConstructorProperties cp = ctr.getAnnotation(ConstructorProperties.class);161String [] ret = cp != null ? cp.value() : null;162return ret;163}164});165}166167//======================================================================168// Public methods169//======================================================================170171/**172* Introspect on a Java Bean and learn about all its properties, exposed173* methods, and events.174* <p>175* If the BeanInfo class for a Java Bean has been previously Introspected176* then the BeanInfo class is retrieved from the BeanInfo cache.177*178* @param beanClass The bean class to be analyzed.179* @return A BeanInfo object describing the target bean.180* @exception IntrospectionException if an exception occurs during181* introspection.182* @see #flushCaches183* @see #flushFromCaches184*/185public static BeanInfo getBeanInfo(Class<?> beanClass)186throws IntrospectionException187{188if (!ReflectUtil.isPackageAccessible(beanClass)) {189return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();190}191ThreadGroupContext context = ThreadGroupContext.getContext();192BeanInfo beanInfo = context.getBeanInfo(beanClass);193if (beanInfo == null) {194beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();195context.putBeanInfo(beanClass, beanInfo);196}197return beanInfo;198}199200/**201* Introspect on a Java bean and learn about all its properties, exposed202* methods, and events, subject to some control flags.203* <p>204* If the BeanInfo class for a Java Bean has been previously Introspected205* based on the same arguments then the BeanInfo class is retrieved206* from the BeanInfo cache.207*208* @param beanClass The bean class to be analyzed.209* @param flags Flags to control the introspection.210* If flags == USE_ALL_BEANINFO then we use all of the BeanInfo211* classes we can discover.212* If flags == IGNORE_IMMEDIATE_BEANINFO then we ignore any213* BeanInfo associated with the specified beanClass.214* If flags == IGNORE_ALL_BEANINFO then we ignore all BeanInfo215* associated with the specified beanClass or any of its216* parent classes.217* @return A BeanInfo object describing the target bean.218* @exception IntrospectionException if an exception occurs during219* introspection.220* @since 1.2221*/222public static BeanInfo getBeanInfo(Class<?> beanClass, int flags)223throws IntrospectionException {224return getBeanInfo(beanClass, null, flags);225}226227/**228* Introspect on a Java bean and learn all about its properties, exposed229* methods, below a given "stop" point.230* <p>231* If the BeanInfo class for a Java Bean has been previously Introspected232* based on the same arguments, then the BeanInfo class is retrieved233* from the BeanInfo cache.234* @return the BeanInfo for the bean235* @param beanClass The bean class to be analyzed.236* @param stopClass The baseclass at which to stop the analysis. Any237* methods/properties/events in the stopClass or in its baseclasses238* will be ignored in the analysis.239* @exception IntrospectionException if an exception occurs during240* introspection.241*/242public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)243throws IntrospectionException {244return getBeanInfo(beanClass, stopClass, USE_ALL_BEANINFO);245}246247/**248* Introspect on a Java Bean and learn about all its properties,249* exposed methods and events, below a given {@code stopClass} point250* subject to some control {@code flags}.251* <dl>252* <dt>USE_ALL_BEANINFO</dt>253* <dd>Any BeanInfo that can be discovered will be used.</dd>254* <dt>IGNORE_IMMEDIATE_BEANINFO</dt>255* <dd>Any BeanInfo associated with the specified {@code beanClass} will be ignored.</dd>256* <dt>IGNORE_ALL_BEANINFO</dt>257* <dd>Any BeanInfo associated with the specified {@code beanClass}258* or any of its parent classes will be ignored.</dd>259* </dl>260* Any methods/properties/events in the {@code stopClass}261* or in its parent classes will be ignored in the analysis.262* <p>263* If the BeanInfo class for a Java Bean has been264* previously introspected based on the same arguments then265* the BeanInfo class is retrieved from the BeanInfo cache.266*267* @param beanClass the bean class to be analyzed268* @param stopClass the parent class at which to stop the analysis269* @param flags flags to control the introspection270* @return a BeanInfo object describing the target bean271* @exception IntrospectionException if an exception occurs during introspection272*273* @since 1.7274*/275public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass,276int flags) throws IntrospectionException {277BeanInfo bi;278if (stopClass == null && flags == USE_ALL_BEANINFO) {279// Same parameters to take advantage of caching.280bi = getBeanInfo(beanClass);281} else {282bi = (new Introspector(beanClass, stopClass, flags)).getBeanInfo();283}284return bi;285286// Old behaviour: Make an independent copy of the BeanInfo.287//return new GenericBeanInfo(bi);288}289290291/**292* Utility method to take a string and convert it to normal Java variable293* name capitalization. This normally means converting the first294* character from upper case to lower case, but in the (unusual) special295* case when there is more than one character and both the first and296* second characters are upper case, we leave it alone.297* <p>298* Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays299* as "URL".300*301* @param name The string to be decapitalized.302* @return The decapitalized version of the string.303*/304public static String decapitalize(String name) {305if (name == null || name.length() == 0) {306return name;307}308if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&309Character.isUpperCase(name.charAt(0))){310return name;311}312char[] chars = name.toCharArray();313chars[0] = Character.toLowerCase(chars[0]);314return new String(chars);315}316317/**318* Gets the list of package names that will be used for319* finding BeanInfo classes.320*321* @return The array of package names that will be searched in322* order to find BeanInfo classes. The default value323* for this array is implementation-dependent; e.g.324* Sun implementation initially sets to {"sun.beans.infos"}.325*/326327public static String[] getBeanInfoSearchPath() {328return ThreadGroupContext.getContext().getBeanInfoFinder().getPackages();329}330331/**332* Change the list of package names that will be used for333* finding BeanInfo classes. The behaviour of334* this method is undefined if parameter path335* is null.336*337* <p>First, if there is a security manager, its {@code checkPropertiesAccess}338* method is called. This could result in a SecurityException.339*340* @param path Array of package names.341* @exception SecurityException if a security manager exists and its342* {@code checkPropertiesAccess} method doesn't allow setting343* of system properties.344* @see SecurityManager#checkPropertiesAccess345*/346347public static void setBeanInfoSearchPath(String[] path) {348@SuppressWarnings("removal")349SecurityManager sm = System.getSecurityManager();350if (sm != null) {351sm.checkPropertiesAccess();352}353ThreadGroupContext.getContext().getBeanInfoFinder().setPackages(path);354}355356357/**358* Flush all of the Introspector's internal caches. This method is359* not normally required. It is normally only needed by advanced360* tools that update existing "Class" objects in-place and need361* to make the Introspector re-analyze existing Class objects.362*363* @since 1.2364*/365public static void flushCaches() {366ThreadGroupContext.getContext().clearBeanInfoCache();367ClassInfo.clear();368}369370/**371* Flush the Introspector's internal cached information for a given class.372* This method is not normally required. It is normally only needed373* by advanced tools that update existing "Class" objects in-place374* and need to make the Introspector re-analyze an existing Class object.375*376* Note that only the direct state associated with the target Class377* object is flushed. We do not flush state for other Class objects378* with the same name, nor do we flush state for any related Class379* objects (such as subclasses), even though their state may include380* information indirectly obtained from the target Class object.381*382* @param clz Class object to be flushed.383* @throws NullPointerException If the Class object is null.384* @since 1.2385*/386public static void flushFromCaches(Class<?> clz) {387if (clz == null) {388throw new NullPointerException();389}390ThreadGroupContext.getContext().removeBeanInfo(clz);391ClassInfo.remove(clz);392}393394//======================================================================395// Private implementation methods396//======================================================================397398private Introspector(Class<?> beanClass, Class<?> stopClass, int flags)399throws IntrospectionException {400this.beanClass = beanClass;401402// Check stopClass is a superClass of startClass.403if (stopClass != null) {404boolean isSuper = false;405for (Class<?> c = beanClass.getSuperclass(); c != null; c = c.getSuperclass()) {406if (c == stopClass) {407isSuper = true;408}409}410if (!isSuper) {411throw new IntrospectionException(stopClass.getName() + " not superclass of " +412beanClass.getName());413}414}415416if (flags == USE_ALL_BEANINFO) {417explicitBeanInfo = findExplicitBeanInfo(beanClass);418}419420Class<?> superClass = beanClass.getSuperclass();421if (superClass != stopClass) {422int newFlags = flags;423if (newFlags == IGNORE_IMMEDIATE_BEANINFO) {424newFlags = USE_ALL_BEANINFO;425}426superBeanInfo = getBeanInfo(superClass, stopClass, newFlags);427}428if (explicitBeanInfo != null) {429additionalBeanInfo = explicitBeanInfo.getAdditionalBeanInfo();430}431if (additionalBeanInfo == null) {432additionalBeanInfo = new BeanInfo[0];433}434}435436/**437* Constructs a GenericBeanInfo class from the state of the Introspector438*/439private BeanInfo getBeanInfo() throws IntrospectionException {440441// the evaluation order here is import, as we evaluate the442// event sets and locate PropertyChangeListeners before we443// look for properties.444BeanDescriptor bd = getTargetBeanDescriptor();445MethodDescriptor[] mds = getTargetMethodInfo();446EventSetDescriptor[] esds = getTargetEventInfo();447PropertyDescriptor[] pds = getTargetPropertyInfo();448449int defaultEvent = getTargetDefaultEventIndex();450int defaultProperty = getTargetDefaultPropertyIndex();451452return new GenericBeanInfo(bd, esds, defaultEvent, pds,453defaultProperty, mds, explicitBeanInfo);454455}456457/**458* Looks for an explicit BeanInfo class that corresponds to the Class.459* First it looks in the existing package that the Class is defined in,460* then it checks to see if the class is its own BeanInfo. Finally,461* the BeanInfo search path is prepended to the class and searched.462*463* @param beanClass the class type of the bean464* @return Instance of an explicit BeanInfo class or null if one isn't found.465*/466private static BeanInfo findExplicitBeanInfo(Class<?> beanClass) {467return ThreadGroupContext.getContext().getBeanInfoFinder().find(beanClass);468}469470/**471* @return An array of PropertyDescriptors describing the editable472* properties supported by the target bean.473*/474475private PropertyDescriptor[] getTargetPropertyInfo() {476477// Check if the bean has its own BeanInfo that will provide478// explicit information.479PropertyDescriptor[] explicitProperties = null;480if (explicitBeanInfo != null) {481explicitProperties = getPropertyDescriptors(this.explicitBeanInfo);482}483484if (explicitProperties == null && superBeanInfo != null) {485// We have no explicit BeanInfo properties. Check with our parent.486addPropertyDescriptors(getPropertyDescriptors(this.superBeanInfo));487}488489for (int i = 0; i < additionalBeanInfo.length; i++) {490addPropertyDescriptors(additionalBeanInfo[i].getPropertyDescriptors());491}492493if (explicitProperties != null) {494// Add the explicit BeanInfo data to our results.495addPropertyDescriptors(explicitProperties);496497} else {498// Apply some reflection to the current class.499for (Map.Entry<String,PropertyInfo> entry : ClassInfo.get(this.beanClass).getProperties().entrySet()) {500addPropertyDescriptor(null != entry.getValue().getIndexed()501? new IndexedPropertyDescriptor(entry, this.propertyChangeSource)502: new PropertyDescriptor(entry, this.propertyChangeSource));503}504JavaBean annotation = this.beanClass.getAnnotation(JavaBean.class);505if ((annotation != null) && !annotation.defaultProperty().isEmpty()) {506this.defaultPropertyName = annotation.defaultProperty();507}508}509processPropertyDescriptors();510511// Allocate and populate the result array.512PropertyDescriptor[] result =513properties.values().toArray(new PropertyDescriptor[properties.size()]);514515// Set the default index.516if (defaultPropertyName != null) {517for (int i = 0; i < result.length; i++) {518if (defaultPropertyName.equals(result[i].getName())) {519defaultPropertyIndex = i;520}521}522}523return result;524}525526private HashMap<String, List<PropertyDescriptor>> pdStore = new HashMap<>();527528/**529* Adds the property descriptor to the list store.530*/531private void addPropertyDescriptor(PropertyDescriptor pd) {532String propName = pd.getName();533List<PropertyDescriptor> list = pdStore.get(propName);534if (list == null) {535list = new ArrayList<>();536pdStore.put(propName, list);537}538if (this.beanClass != pd.getClass0()) {539// replace existing property descriptor540// only if we have types to resolve541// in the context of this.beanClass542Method read = pd.getReadMethod();543Method write = pd.getWriteMethod();544boolean cls = true;545if (read != null) cls = cls && read.getGenericReturnType() instanceof Class;546if (write != null) cls = cls && write.getGenericParameterTypes()[0] instanceof Class;547if (pd instanceof IndexedPropertyDescriptor) {548IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;549Method readI = ipd.getIndexedReadMethod();550Method writeI = ipd.getIndexedWriteMethod();551if (readI != null) cls = cls && readI.getGenericReturnType() instanceof Class;552if (writeI != null) cls = cls && writeI.getGenericParameterTypes()[1] instanceof Class;553if (!cls) {554pd = new IndexedPropertyDescriptor(ipd);555pd.updateGenericsFor(this.beanClass);556}557}558else if (!cls) {559pd = new PropertyDescriptor(pd);560pd.updateGenericsFor(this.beanClass);561}562}563list.add(pd);564}565566private void addPropertyDescriptors(PropertyDescriptor[] descriptors) {567if (descriptors != null) {568for (PropertyDescriptor descriptor : descriptors) {569addPropertyDescriptor(descriptor);570}571}572}573574private PropertyDescriptor[] getPropertyDescriptors(BeanInfo info) {575PropertyDescriptor[] descriptors = info.getPropertyDescriptors();576int index = info.getDefaultPropertyIndex();577if ((0 <= index) && (index < descriptors.length)) {578this.defaultPropertyName = descriptors[index].getName();579}580return descriptors;581}582583/**584* Populates the property descriptor table by merging the585* lists of Property descriptors.586*/587private void processPropertyDescriptors() {588if (properties == null) {589properties = new TreeMap<>();590}591592List<PropertyDescriptor> list;593594PropertyDescriptor pd, gpd, spd;595IndexedPropertyDescriptor ipd, igpd, ispd;596597for (List<PropertyDescriptor> propertyDescriptors : pdStore.values()) {598pd = null; gpd = null; spd = null;599ipd = null; igpd = null; ispd = null;600601list = propertyDescriptors;602603// First pass. Find the latest getter method. Merge properties604// of previous getter methods.605for (int i = 0; i < list.size(); i++) {606pd = list.get(i);607if (pd instanceof IndexedPropertyDescriptor) {608ipd = (IndexedPropertyDescriptor)pd;609if (ipd.getIndexedReadMethod() != null) {610if (igpd != null) {611igpd = new IndexedPropertyDescriptor(igpd, ipd);612} else {613igpd = ipd;614}615}616} else {617if (pd.getReadMethod() != null) {618String pdName = pd.getReadMethod().getName();619if (gpd != null) {620// Don't replace the existing read621// method if it starts with "is"622String gpdName = gpd.getReadMethod().getName();623if (gpdName.equals(pdName) || !gpdName.startsWith(IS_PREFIX)) {624gpd = new PropertyDescriptor(gpd, pd);625}626} else {627gpd = pd;628}629}630}631}632633// Second pass. Find the latest setter method which634// has the same type as the getter method.635for (int i = 0; i < list.size(); i++) {636pd = list.get(i);637if (pd instanceof IndexedPropertyDescriptor) {638ipd = (IndexedPropertyDescriptor)pd;639if (ipd.getIndexedWriteMethod() != null) {640if (igpd != null) {641if (isAssignable(igpd.getIndexedPropertyType(), ipd.getIndexedPropertyType())) {642if (ispd != null) {643ispd = new IndexedPropertyDescriptor(ispd, ipd);644} else {645ispd = ipd;646}647}648} else {649if (ispd != null) {650ispd = new IndexedPropertyDescriptor(ispd, ipd);651} else {652ispd = ipd;653}654}655}656} else {657if (pd.getWriteMethod() != null) {658if (gpd != null) {659if (isAssignable(gpd.getPropertyType(), pd.getPropertyType())) {660if (spd != null) {661spd = new PropertyDescriptor(spd, pd);662} else {663spd = pd;664}665}666} else {667if (spd != null) {668spd = new PropertyDescriptor(spd, pd);669} else {670spd = pd;671}672}673}674}675}676677// At this stage we should have either PDs or IPDs for the678// representative getters and setters. The order at which the679// property descriptors are determined represent the680// precedence of the property ordering.681pd = null; ipd = null;682683if (igpd != null && ispd != null) {684// Complete indexed properties set685// Merge any classic property descriptors686if ((gpd == spd) || (gpd == null)) {687pd = spd;688} else if (spd == null) {689pd = gpd;690} else if (spd instanceof IndexedPropertyDescriptor) {691pd = mergePropertyWithIndexedProperty(gpd, (IndexedPropertyDescriptor) spd);692} else if (gpd instanceof IndexedPropertyDescriptor) {693pd = mergePropertyWithIndexedProperty(spd, (IndexedPropertyDescriptor) gpd);694} else {695pd = mergePropertyDescriptor(gpd, spd);696}697if (igpd == ispd) {698ipd = igpd;699} else {700ipd = mergePropertyDescriptor(igpd, ispd);701}702if (pd == null) {703pd = ipd;704} else {705Class<?> propType = pd.getPropertyType();706Class<?> ipropType = ipd.getIndexedPropertyType();707if (propType.isArray() && propType.getComponentType() == ipropType) {708pd = pd.getClass0().isAssignableFrom(ipd.getClass0())709? new IndexedPropertyDescriptor(pd, ipd)710: new IndexedPropertyDescriptor(ipd, pd);711} else if (pd.getClass0().isAssignableFrom(ipd.getClass0())) {712pd = pd.getClass0().isAssignableFrom(ipd.getClass0())713? new PropertyDescriptor(pd, ipd)714: new PropertyDescriptor(ipd, pd);715} else {716pd = ipd;717}718}719} else if (gpd != null && spd != null) {720if (igpd != null) {721gpd = mergePropertyWithIndexedProperty(gpd, igpd);722}723if (ispd != null) {724spd = mergePropertyWithIndexedProperty(spd, ispd);725}726// Complete simple properties set727if (gpd == spd) {728pd = gpd;729} else if (spd instanceof IndexedPropertyDescriptor) {730pd = mergePropertyWithIndexedProperty(gpd, (IndexedPropertyDescriptor) spd);731} else if (gpd instanceof IndexedPropertyDescriptor) {732pd = mergePropertyWithIndexedProperty(spd, (IndexedPropertyDescriptor) gpd);733} else {734pd = mergePropertyDescriptor(gpd, spd);735}736} else if (ispd != null) {737// indexed setter738pd = ispd;739// Merge any classic property descriptors740if (spd != null) {741pd = mergePropertyDescriptor(ispd, spd);742}743if (gpd != null) {744pd = mergePropertyDescriptor(ispd, gpd);745}746} else if (igpd != null) {747// indexed getter748pd = igpd;749// Merge any classic property descriptors750if (gpd != null) {751pd = mergePropertyDescriptor(igpd, gpd);752}753if (spd != null) {754pd = mergePropertyDescriptor(igpd, spd);755}756} else if (spd != null) {757// simple setter758pd = spd;759} else if (gpd != null) {760// simple getter761pd = gpd;762}763764// Very special case to ensure that an IndexedPropertyDescriptor765// doesn't contain less information than the enclosed766// PropertyDescriptor. If it does, then recreate as a767// PropertyDescriptor. See 4168833768if (pd instanceof IndexedPropertyDescriptor) {769ipd = (IndexedPropertyDescriptor)pd;770if (ipd.getIndexedReadMethod() == null && ipd.getIndexedWriteMethod() == null) {771pd = new PropertyDescriptor(ipd);772}773}774775// Find the first property descriptor776// which does not have getter and setter methods.777// See regression bug 4984912.778if ( (pd == null) && (list.size() > 0) ) {779pd = list.get(0);780}781782if (pd != null) {783properties.put(pd.getName(), pd);784}785}786}787788private static boolean isAssignable(Class<?> current, Class<?> candidate) {789return ((current == null) || (candidate == null)) ? current == candidate : current.isAssignableFrom(candidate);790}791792private PropertyDescriptor mergePropertyWithIndexedProperty(PropertyDescriptor pd, IndexedPropertyDescriptor ipd) {793Class<?> type = pd.getPropertyType();794if (type.isArray() && (type.getComponentType() == ipd.getIndexedPropertyType())) {795return pd.getClass0().isAssignableFrom(ipd.getClass0())796? new IndexedPropertyDescriptor(pd, ipd)797: new IndexedPropertyDescriptor(ipd, pd);798}799return pd;800}801802/**803* Adds the property descriptor to the indexedproperty descriptor only if the804* types are the same.805*806* The most specific property descriptor will take precedence.807*/808private PropertyDescriptor mergePropertyDescriptor(IndexedPropertyDescriptor ipd,809PropertyDescriptor pd) {810PropertyDescriptor result = null;811812Class<?> propType = pd.getPropertyType();813Class<?> ipropType = ipd.getIndexedPropertyType();814815if (propType.isArray() && propType.getComponentType() == ipropType) {816if (pd.getClass0().isAssignableFrom(ipd.getClass0())) {817result = new IndexedPropertyDescriptor(pd, ipd);818} else {819result = new IndexedPropertyDescriptor(ipd, pd);820}821} else if ((ipd.getReadMethod() == null) && (ipd.getWriteMethod() == null)) {822if (pd.getClass0().isAssignableFrom(ipd.getClass0())) {823result = new PropertyDescriptor(pd, ipd);824} else {825result = new PropertyDescriptor(ipd, pd);826}827} else {828// Cannot merge the pd because of type mismatch829// Return the most specific pd830if (pd.getClass0().isAssignableFrom(ipd.getClass0())) {831result = ipd;832} else {833result = pd;834// Try to add methods which may have been lost in the type change835// See 4168833836Method write = result.getWriteMethod();837Method read = result.getReadMethod();838839if (read == null && write != null) {840read = findMethod(result.getClass0(),841GET_PREFIX + NameGenerator.capitalize(result.getName()), 0);842if (read != null) {843try {844result.setReadMethod(read);845} catch (IntrospectionException ex) {846// no consequences for failure.847}848}849}850if (write == null && read != null) {851write = findMethod(result.getClass0(),852SET_PREFIX + NameGenerator.capitalize(result.getName()), 1,853new Class<?>[] { FeatureDescriptor.getReturnType(result.getClass0(), read) });854if (write != null) {855try {856result.setWriteMethod(write);857} catch (IntrospectionException ex) {858// no consequences for failure.859}860}861}862}863}864return result;865}866867// Handle regular pd merge868private PropertyDescriptor mergePropertyDescriptor(PropertyDescriptor pd1,869PropertyDescriptor pd2) {870if (pd1.getClass0().isAssignableFrom(pd2.getClass0())) {871return new PropertyDescriptor(pd1, pd2);872} else {873return new PropertyDescriptor(pd2, pd1);874}875}876877// Handle regular ipd merge878private IndexedPropertyDescriptor mergePropertyDescriptor(IndexedPropertyDescriptor ipd1,879IndexedPropertyDescriptor ipd2) {880if (ipd1.getClass0().isAssignableFrom(ipd2.getClass0())) {881return new IndexedPropertyDescriptor(ipd1, ipd2);882} else {883return new IndexedPropertyDescriptor(ipd2, ipd1);884}885}886887/**888* @return An array of EventSetDescriptors describing the kinds of889* events fired by the target bean.890*/891private EventSetDescriptor[] getTargetEventInfo() throws IntrospectionException {892if (events == null) {893events = new HashMap<>();894}895896// Check if the bean has its own BeanInfo that will provide897// explicit information.898EventSetDescriptor[] explicitEvents = null;899if (explicitBeanInfo != null) {900explicitEvents = explicitBeanInfo.getEventSetDescriptors();901int ix = explicitBeanInfo.getDefaultEventIndex();902if (ix >= 0 && ix < explicitEvents.length) {903defaultEventName = explicitEvents[ix].getName();904}905}906907if (explicitEvents == null && superBeanInfo != null) {908// We have no explicit BeanInfo events. Check with our parent.909EventSetDescriptor[] supers = superBeanInfo.getEventSetDescriptors();910for (int i = 0 ; i < supers.length; i++) {911addEvent(supers[i]);912}913int ix = superBeanInfo.getDefaultEventIndex();914if (ix >= 0 && ix < supers.length) {915defaultEventName = supers[ix].getName();916}917}918919for (int i = 0; i < additionalBeanInfo.length; i++) {920EventSetDescriptor[] additional = additionalBeanInfo[i].getEventSetDescriptors();921if (additional != null) {922for (int j = 0 ; j < additional.length; j++) {923addEvent(additional[j]);924}925}926}927928if (explicitEvents != null) {929// Add the explicit explicitBeanInfo data to our results.930for (int i = 0 ; i < explicitEvents.length; i++) {931addEvent(explicitEvents[i]);932}933934} else {935// Apply some reflection to the current class.936for (Map.Entry<String,EventSetInfo> entry : ClassInfo.get(this.beanClass).getEventSets().entrySet()) {937// generate a list of Method objects for each of the target methods:938List<Method> methods = new ArrayList<>();939for (Method method : ClassInfo.get(entry.getValue().getListenerType()).getMethods()) {940if (isEventHandler(method)) {941methods.add(method);942}943}944addEvent(new EventSetDescriptor(945entry.getKey(),946entry.getValue(),947methods.toArray(new Method[methods.size()])));948}949JavaBean annotation = this.beanClass.getAnnotation(JavaBean.class);950if ((annotation != null) && !annotation.defaultEventSet().isEmpty()) {951this.defaultEventName = annotation.defaultEventSet();952}953}954EventSetDescriptor[] result;955if (events.size() == 0) {956result = EMPTY_EVENTSETDESCRIPTORS;957} else {958// Allocate and populate the result array.959result = new EventSetDescriptor[events.size()];960result = events.values().toArray(result);961// Set the default index.962if (defaultEventName != null) {963for (int i = 0; i < result.length; i++) {964if (defaultEventName.equals(result[i].getName())) {965defaultEventIndex = i;966}967}968}969}970return result;971}972973private void addEvent(EventSetDescriptor esd) {974String key = esd.getName();975if (esd.getName().equals("propertyChange")) {976propertyChangeSource = true;977}978EventSetDescriptor old = events.get(key);979if (old == null) {980events.put(key, esd);981return;982}983EventSetDescriptor composite = new EventSetDescriptor(old, esd);984events.put(key, composite);985}986987/**988* @return An array of MethodDescriptors describing the private989* methods supported by the target bean.990*/991private MethodDescriptor[] getTargetMethodInfo() {992if (methods == null) {993methods = new HashMap<>(100);994}995996// Check if the bean has its own BeanInfo that will provide997// explicit information.998MethodDescriptor[] explicitMethods = null;999if (explicitBeanInfo != null) {1000explicitMethods = explicitBeanInfo.getMethodDescriptors();1001}10021003if (explicitMethods == null && superBeanInfo != null) {1004// We have no explicit BeanInfo methods. Check with our parent.1005MethodDescriptor[] supers = superBeanInfo.getMethodDescriptors();1006for (int i = 0 ; i < supers.length; i++) {1007addMethod(supers[i]);1008}1009}10101011for (int i = 0; i < additionalBeanInfo.length; i++) {1012MethodDescriptor[] additional = additionalBeanInfo[i].getMethodDescriptors();1013if (additional != null) {1014for (int j = 0 ; j < additional.length; j++) {1015addMethod(additional[j]);1016}1017}1018}10191020if (explicitMethods != null) {1021// Add the explicit explicitBeanInfo data to our results.1022for (int i = 0 ; i < explicitMethods.length; i++) {1023addMethod(explicitMethods[i]);1024}10251026} else {1027// Apply some reflection to the current class.1028for (Method method : ClassInfo.get(this.beanClass).getMethods()) {1029addMethod(new MethodDescriptor(method));1030}1031}10321033// Allocate and populate the result array.1034MethodDescriptor[] result = new MethodDescriptor[methods.size()];1035result = methods.values().toArray(result);10361037return result;1038}10391040private void addMethod(MethodDescriptor md) {1041// We have to be careful here to distinguish method by both name1042// and argument lists.1043// This method gets called a *lot, so we try to be efficient.1044String name = md.getName();10451046MethodDescriptor old = methods.get(name);1047if (old == null) {1048// This is the common case.1049methods.put(name, md);1050return;1051}10521053// We have a collision on method names. This is rare.10541055// Check if old and md have the same type.1056String[] p1 = md.getParamNames();1057String[] p2 = old.getParamNames();10581059boolean match = false;1060if (p1.length == p2.length) {1061match = true;1062for (int i = 0; i < p1.length; i++) {1063if (p1[i] != p2[i]) {1064match = false;1065break;1066}1067}1068}1069if (match) {1070MethodDescriptor composite = new MethodDescriptor(old, md);1071methods.put(name, composite);1072return;1073}10741075// We have a collision on method names with different type signatures.1076// This is very rare.10771078String longKey = makeQualifiedMethodName(name, p1);1079old = methods.get(longKey);1080if (old == null) {1081methods.put(longKey, md);1082return;1083}1084MethodDescriptor composite = new MethodDescriptor(old, md);1085methods.put(longKey, composite);1086}10871088/**1089* Creates a key for a method in a method cache.1090*/1091private static String makeQualifiedMethodName(String name, String[] params) {1092StringBuilder sb = new StringBuilder(name);1093sb.append('=');1094for (int i = 0; i < params.length; i++) {1095sb.append(':');1096sb.append(params[i]);1097}1098return sb.toString();1099}11001101private int getTargetDefaultEventIndex() {1102return defaultEventIndex;1103}11041105private int getTargetDefaultPropertyIndex() {1106return defaultPropertyIndex;1107}11081109private BeanDescriptor getTargetBeanDescriptor() {1110// Use explicit info, if available,1111if (explicitBeanInfo != null) {1112BeanDescriptor bd = explicitBeanInfo.getBeanDescriptor();1113if (bd != null) {1114return (bd);1115}1116}1117// OK, fabricate a default BeanDescriptor.1118return new BeanDescriptor(this.beanClass, findCustomizerClass(this.beanClass));1119}11201121private static Class<?> findCustomizerClass(Class<?> type) {1122String name = type.getName() + "Customizer";1123try {1124type = ClassFinder.findClass(name, type.getClassLoader());1125// Each customizer should inherit java.awt.Component and implement java.beans.Customizer1126// according to the section 9.3 of JavaBeans specification1127if (Component.class.isAssignableFrom(type) && Customizer.class.isAssignableFrom(type)) {1128return type;1129}1130}1131catch (Exception exception) {1132// ignore any exceptions1133}1134return null;1135}11361137private boolean isEventHandler(Method m) {1138// We assume that a method is an event handler if it has a single1139// argument, whose type inherit from java.util.Event.1140Type[] argTypes = m.getGenericParameterTypes();1141if (argTypes.length != 1) {1142return false;1143}1144return isSubclass(TypeResolver.erase(TypeResolver.resolveInClass(beanClass, argTypes[0])), EventObject.class);1145}11461147//======================================================================1148// Package private support methods.1149//======================================================================11501151/**1152* Internal support for finding a target methodName with a given1153* parameter list on a given class.1154*/1155private static Method internalFindMethod(Class<?> start, String methodName,1156int argCount, Class<?>[] args) {1157// For overriden methods we need to find the most derived version.1158// So we start with the given class and walk up the superclass chain.1159for (Class<?> cl = start; cl != null; cl = cl.getSuperclass()) {1160for (Method method : ClassInfo.get(cl).getMethods()) {1161// make sure method signature matches.1162if (method.getName().equals(methodName)) {1163Type[] params = method.getGenericParameterTypes();1164if (params.length == argCount) {1165if (args != null) {1166boolean different = false;1167if (argCount > 0) {1168for (int j = 0; j < argCount; j++) {1169if (TypeResolver.erase(TypeResolver.resolveInClass(start, params[j])) != args[j]) {1170different = true;1171continue;1172}1173}1174if (different) {1175continue;1176}1177}1178}1179return method;1180}1181}1182}1183}1184// Now check any inherited interfaces. This is necessary both when1185// the argument class is itself an interface, and when the argument1186// class is an abstract class.1187Class<?>[] ifcs = start.getInterfaces();1188for (int i = 0 ; i < ifcs.length; i++) {1189// Note: The original implementation had both methods calling1190// the 3 arg method. This is preserved but perhaps it should1191// pass the args array instead of null.1192Method method = internalFindMethod(ifcs[i], methodName, argCount, null);1193if (method != null) {1194return method;1195}1196}1197return null;1198}11991200/**1201* Find a target methodName on a given class.1202*/1203static Method findMethod(Class<?> cls, String methodName, int argCount) {1204return findMethod(cls, methodName, argCount, null);1205}12061207/**1208* Find a target methodName with specific parameter list on a given class.1209* <p>1210* Used in the contructors of the EventSetDescriptor,1211* PropertyDescriptor and the IndexedPropertyDescriptor.1212* <p>1213* @param cls The Class object on which to retrieve the method.1214* @param methodName Name of the method.1215* @param argCount Number of arguments for the desired method.1216* @param args Array of argument types for the method.1217* @return the method or null if not found1218*/1219static Method findMethod(Class<?> cls, String methodName, int argCount,1220Class<?>[] args) {1221if (methodName == null) {1222return null;1223}1224return internalFindMethod(cls, methodName, argCount, args);1225}12261227/**1228* Return true if class a is either equivalent to class b, or1229* if class a is a subclass of class b, i.e. if a either "extends"1230* or "implements" b.1231* Note tht either or both "Class" objects may represent interfaces.1232*/1233static boolean isSubclass(Class<?> a, Class<?> b) {1234// We rely on the fact that for any given java class or1235// primtitive type there is a unqiue Class object, so1236// we can use object equivalence in the comparisons.1237if (a == b) {1238return true;1239}1240if (a == null || b == null) {1241return false;1242}1243for (Class<?> x = a; x != null; x = x.getSuperclass()) {1244if (x == b) {1245return true;1246}1247if (b.isInterface()) {1248Class<?>[] interfaces = x.getInterfaces();1249for (int i = 0; i < interfaces.length; i++) {1250if (isSubclass(interfaces[i], b)) {1251return true;1252}1253}1254}1255}1256return false;1257}12581259/**1260* Try to create an instance of a named class.1261* First try the classloader of "sibling", then try the system1262* classloader then the class loader of the current Thread.1263*/1264@SuppressWarnings("deprecation")1265static Object instantiate(Class<?> sibling, String className)1266throws InstantiationException, IllegalAccessException,1267NoSuchMethodException, InvocationTargetException,1268ClassNotFoundException {1269// First check with sibling's classloader (if any).1270ClassLoader cl = sibling.getClassLoader();1271Class<?> cls = ClassFinder.findClass(className, cl);1272return cls.newInstance();1273}12741275} // end class Introspector12761277//===========================================================================12781279/**1280* Package private implementation support class for Introspector's1281* internal use.1282* <p>1283* Mostly this is used as a placeholder for the descriptors.1284*/12851286class GenericBeanInfo extends SimpleBeanInfo {12871288private BeanDescriptor beanDescriptor;1289private EventSetDescriptor[] events;1290private int defaultEvent;1291private PropertyDescriptor[] properties;1292private int defaultProperty;1293private MethodDescriptor[] methods;1294private Reference<BeanInfo> targetBeanInfoRef;12951296public GenericBeanInfo(BeanDescriptor beanDescriptor,1297EventSetDescriptor[] events, int defaultEvent,1298PropertyDescriptor[] properties, int defaultProperty,1299MethodDescriptor[] methods, BeanInfo targetBeanInfo) {1300this.beanDescriptor = beanDescriptor;1301this.events = events;1302this.defaultEvent = defaultEvent;1303this.properties = properties;1304this.defaultProperty = defaultProperty;1305this.methods = methods;1306this.targetBeanInfoRef = (targetBeanInfo != null)1307? new SoftReference<>(targetBeanInfo)1308: null;1309}13101311/**1312* Package-private dup constructor1313* This must isolate the new object from any changes to the old object.1314*/1315GenericBeanInfo(GenericBeanInfo old) {13161317beanDescriptor = new BeanDescriptor(old.beanDescriptor);1318if (old.events != null) {1319int len = old.events.length;1320events = new EventSetDescriptor[len];1321for (int i = 0; i < len; i++) {1322events[i] = new EventSetDescriptor(old.events[i]);1323}1324}1325defaultEvent = old.defaultEvent;1326if (old.properties != null) {1327int len = old.properties.length;1328properties = new PropertyDescriptor[len];1329for (int i = 0; i < len; i++) {1330PropertyDescriptor oldp = old.properties[i];1331if (oldp instanceof IndexedPropertyDescriptor) {1332properties[i] = new IndexedPropertyDescriptor(1333(IndexedPropertyDescriptor) oldp);1334} else {1335properties[i] = new PropertyDescriptor(oldp);1336}1337}1338}1339defaultProperty = old.defaultProperty;1340if (old.methods != null) {1341int len = old.methods.length;1342methods = new MethodDescriptor[len];1343for (int i = 0; i < len; i++) {1344methods[i] = new MethodDescriptor(old.methods[i]);1345}1346}1347this.targetBeanInfoRef = old.targetBeanInfoRef;1348}13491350public PropertyDescriptor[] getPropertyDescriptors() {1351return properties;1352}13531354public int getDefaultPropertyIndex() {1355return defaultProperty;1356}13571358public EventSetDescriptor[] getEventSetDescriptors() {1359return events;1360}13611362public int getDefaultEventIndex() {1363return defaultEvent;1364}13651366public MethodDescriptor[] getMethodDescriptors() {1367return methods;1368}13691370public BeanDescriptor getBeanDescriptor() {1371return beanDescriptor;1372}13731374public java.awt.Image getIcon(int iconKind) {1375BeanInfo targetBeanInfo = getTargetBeanInfo();1376if (targetBeanInfo != null) {1377return targetBeanInfo.getIcon(iconKind);1378}1379return super.getIcon(iconKind);1380}13811382private BeanInfo getTargetBeanInfo() {1383if (this.targetBeanInfoRef == null) {1384return null;1385}1386BeanInfo targetBeanInfo = this.targetBeanInfoRef.get();1387if (targetBeanInfo == null) {1388targetBeanInfo = ThreadGroupContext.getContext().getBeanInfoFinder()1389.find(this.beanDescriptor.getBeanClass());1390if (targetBeanInfo != null) {1391this.targetBeanInfoRef = new SoftReference<>(targetBeanInfo);1392}1393}1394return targetBeanInfo;1395}1396}139713981399