1. /*
  2. * @(#)Introspector.java 1.100 01/02/09
  3. *
  4. * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package java.beans;
  11. import java.lang.reflect.*;
  12. import java.security.*;
  13. /**
  14. * The Introspector class provides a standard way for tools to learn about
  15. * the properties, events, and methods supported by a target Java Bean.
  16. * <p>
  17. * For each of those three kinds of information, the Introspector will
  18. * separately analyze the bean's class and superclasses looking for
  19. * either explicit or implicit information and use that information to
  20. * build a BeanInfo object that comprehensively describes the target bean.
  21. * <p>
  22. * For each class "Foo", explicit information may be available if there exists
  23. * a corresponding "FooBeanInfo" class that provides a non-null value when
  24. * queried for the information. We first look for the BeanInfo class by
  25. * taking the full package-qualified name of the target bean class and
  26. * appending "BeanInfo" to form a new class name. If this fails, then
  27. * we take the final classname component of this name, and look for that
  28. * class in each of the packages specified in the BeanInfo package search
  29. * path.
  30. * <p>
  31. * Thus for a class such as "sun.xyz.OurButton" we would first look for a
  32. * BeanInfo class called "sun.xyz.OurButtonBeanInfo" and if that failed we'd
  33. * look in each package in the BeanInfo search path for an OurButtonBeanInfo
  34. * class. With the default search path, this would mean looking for
  35. * "sun.beans.infos.OurButtonBeanInfo".
  36. * <p>
  37. * If a class provides explicit BeanInfo about itself then we add that to
  38. * the BeanInfo information we obtained from analyzing any derived classes,
  39. * but we regard the explicit information as being definitive for the current
  40. * class and its base classes, and do not proceed any further up the superclass
  41. * chain.
  42. * <p>
  43. * If we don't find explicit BeanInfo on a class, we use low-level
  44. * reflection to study the methods of the class and apply standard design
  45. * patterns to identify property accessors, event sources, or public
  46. * methods. We then proceed to analyze the class's superclass and add
  47. * in the information from it (and possibly on up the superclass chain).
  48. * <P>
  49. * For more information about introspection and design patterns, please
  50. * consult the
  51. * <a href="http://java.sun.com/beans/docs/index.html">JavaBeans specification</a>.
  52. */
  53. public class Introspector {
  54. // Flags that can be used to control getBeanInfo:
  55. public final static int USE_ALL_BEANINFO = 1;
  56. public final static int IGNORE_IMMEDIATE_BEANINFO = 2;
  57. public final static int IGNORE_ALL_BEANINFO = 3;
  58. //======================================================================
  59. // Public methods
  60. //======================================================================
  61. /**
  62. * Introspect on a Java bean and learn about all its properties, exposed
  63. * methods, and events.
  64. *
  65. * @param beanClass The bean class to be analyzed.
  66. * @return A BeanInfo object describing the target bean.
  67. * @exception IntrospectionException if an exception occurs during
  68. * introspection.
  69. */
  70. public static BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException {
  71. GenericBeanInfo bi = (GenericBeanInfo)beanInfoCache.get(beanClass);
  72. if (bi == null) {
  73. bi = (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
  74. beanInfoCache.put(beanClass, bi);
  75. }
  76. // Make an independent copy of the BeanInfo.
  77. return new GenericBeanInfo(bi);
  78. }
  79. /**
  80. * Introspect on a Java bean and learn about all its properties, exposed
  81. * methods, and events, subject to some control flags.
  82. *
  83. * @param beanClass The bean class to be analyzed.
  84. * @param flags Flags to control the introspection.
  85. * If flags == USE_ALL_BEANINFO then we use all of the BeanInfo
  86. * classes we can discover.
  87. * If flags == IGNORE_IMMEDIATE_BEANINFO then we ignore any
  88. * BeanInfo associated with the specified beanClass.
  89. * If flags == IGNORE_ALL_BEANINFO then we ignore all BeanInfo
  90. * associated with the specified beanClass or any of its
  91. * parent classes.
  92. * @return A BeanInfo object describing the target bean.
  93. * @exception IntrospectionException if an exception occurs during
  94. * introspection.
  95. */
  96. public static BeanInfo getBeanInfo(Class beanClass, int flags)
  97. throws IntrospectionException {
  98. // We don't use the cache.
  99. GenericBeanInfo bi = (new Introspector(beanClass, null, flags)).getBeanInfo();
  100. // Make an independent copy of the BeanInfo.
  101. return new GenericBeanInfo(bi);
  102. }
  103. /**
  104. * Introspect on a Java bean and learn all about its properties, exposed
  105. * methods, below a given "stop" point.
  106. *
  107. * @param bean The bean class to be analyzed.
  108. * @param stopClass The baseclass at which to stop the analysis. Any
  109. * methods/properties/events in the stopClass or in its baseclasses
  110. * will be ignored in the analysis.
  111. * @exception IntrospectionException if an exception occurs during
  112. * introspection.
  113. */
  114. public static BeanInfo getBeanInfo(Class beanClass, Class stopClass)
  115. throws IntrospectionException {
  116. GenericBeanInfo bi = (new Introspector(beanClass, stopClass, USE_ALL_BEANINFO)).getBeanInfo();
  117. // Make an independent copy of the BeanInfo.
  118. return new GenericBeanInfo(bi);
  119. }
  120. /**
  121. * Utility method to take a string and convert it to normal Java variable
  122. * name capitalization. This normally means converting the first
  123. * character from upper case to lower case, but in the (unusual) special
  124. * case when there is more than one character and both the first and
  125. * second characters are upper case, we leave it alone.
  126. * <p>
  127. * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
  128. * as "URL".
  129. *
  130. * @param name The string to be decapitalized.
  131. * @return The decapitalized version of the string.
  132. */
  133. public static String decapitalize(String name) {
  134. if (name == null || name.length() == 0) {
  135. return name;
  136. }
  137. if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
  138. Character.isUpperCase(name.charAt(0))){
  139. return name;
  140. }
  141. char chars[] = name.toCharArray();
  142. chars[0] = Character.toLowerCase(chars[0]);
  143. return new String(chars);
  144. }
  145. /**
  146. * Gets the list of package names that will be used for
  147. * finding BeanInfo classes.
  148. *
  149. * @return The array of package names that will be searched in
  150. * order to find BeanInfo classes.
  151. * <p> This is initially set to {"sun.beans.infos"}.
  152. */
  153. public static synchronized String[] getBeanInfoSearchPath() {
  154. // Return a copy of the searchPath.
  155. String result[] = new String[searchPath.length];
  156. for (int i = 0; i < searchPath.length; i++) {
  157. result[i] = searchPath[i];
  158. }
  159. return result;
  160. }
  161. /**
  162. * Change the list of package names that will be used for
  163. * finding BeanInfo classes.
  164. *
  165. * <p>First, if there is a security manager, its <code>checkPropertiesAccess</code>
  166. * method is called. This could result in a SecurityException.
  167. *
  168. * @param path Array of package names.
  169. * @exception SecurityException if a security manager exists and its
  170. * <code>checkPropertiesAccess</code> method doesn't allow setting
  171. * of system properties.
  172. * @see SecurityManager#checkPropertiesAccess
  173. */
  174. public static synchronized void setBeanInfoSearchPath(String path[]) {
  175. SecurityManager sm = System.getSecurityManager();
  176. if (sm != null) {
  177. sm.checkPropertiesAccess();
  178. }
  179. searchPath = path;
  180. }
  181. /**
  182. * Flush all of the Introspector's internal caches. This method is
  183. * not normally required. It is normally only needed by advanced
  184. * tools that update existing "Class" objects in-place and need
  185. * to make the Introspector re-analyze existing Class objects.
  186. */
  187. public static void flushCaches() {
  188. beanInfoCache.clear();
  189. declaredMethodCache.clear();
  190. }
  191. /**
  192. * Flush the Introspector's internal cached information for a given class.
  193. * This method is not normally required. It is normally only needed
  194. * by advanced tools that update existing "Class" objects in-place
  195. * and need to make the Introspector re-analyze an existing Class object.
  196. *
  197. * Note that only the direct state associated with the target Class
  198. * object is flushed. We do not flush state for other Class objects
  199. * with the same name, nor do we flush state for any related Class
  200. * objects (such as subclasses), even though their state may include
  201. * information indirectly obtained from the target Class object.
  202. *
  203. * @param clz Class object to be flushed.
  204. */
  205. public static void flushFromCaches(Class clz) {
  206. beanInfoCache.remove(clz);
  207. declaredMethodCache.remove(clz);
  208. }
  209. //======================================================================
  210. // Private implementation methods
  211. //======================================================================
  212. private Introspector(Class beanClass, Class stopClass, int flags)
  213. throws IntrospectionException {
  214. this.beanClass = beanClass;
  215. // Check stopClass is a superClass of startClass.
  216. if (stopClass != null) {
  217. boolean isSuper = false;
  218. for (Class c = beanClass.getSuperclass(); c != null; c = c.getSuperclass()) {
  219. if (c == stopClass) {
  220. isSuper = true;
  221. }
  222. }
  223. if (!isSuper) {
  224. throw new IntrospectionException(stopClass.getName() + " not superclass of " +
  225. beanClass.getName());
  226. }
  227. }
  228. if (flags == USE_ALL_BEANINFO) {
  229. informant = findInformant(beanClass);
  230. }
  231. Class superClass = beanClass.getSuperclass();
  232. if (superClass != stopClass) {
  233. int newFlags = flags;
  234. if (newFlags == IGNORE_IMMEDIATE_BEANINFO) {
  235. newFlags = USE_ALL_BEANINFO;
  236. }
  237. if (stopClass == null && newFlags == USE_ALL_BEANINFO) {
  238. // We avoid going through getBeanInfo as we don't need
  239. // it do copy the BeanInfo.
  240. superBeanInfo = (BeanInfo)beanInfoCache.get(superClass);
  241. if (superBeanInfo == null) {
  242. Introspector ins = new Introspector(superClass, null, USE_ALL_BEANINFO);
  243. superBeanInfo = ins.getBeanInfo();
  244. beanInfoCache.put(superClass, superBeanInfo);
  245. }
  246. } else {
  247. Introspector ins = new Introspector(superClass, stopClass, newFlags);
  248. superBeanInfo = ins.getBeanInfo();
  249. }
  250. }
  251. if (informant != null) {
  252. additionalBeanInfo = informant.getAdditionalBeanInfo();
  253. }
  254. if (additionalBeanInfo == null) {
  255. additionalBeanInfo = new BeanInfo[0];
  256. }
  257. }
  258. private GenericBeanInfo getBeanInfo() throws IntrospectionException {
  259. // the evaluation order here is import, as we evaluate the
  260. // event sets and locate PropertyChangeListeners before we
  261. // look for properties.
  262. BeanDescriptor bd = getTargetBeanDescriptor();
  263. EventSetDescriptor esds[] = getTargetEventInfo();
  264. int defaultEvent = getTargetDefaultEventIndex();
  265. PropertyDescriptor pds[] = getTargetPropertyInfo();
  266. int defaultProperty = getTargetDefaultPropertyIndex();
  267. MethodDescriptor mds[] = getTargetMethodInfo();
  268. return new GenericBeanInfo(bd, esds, defaultEvent, pds,
  269. defaultProperty, mds, informant);
  270. }
  271. private static synchronized BeanInfo findInformant(Class beanClass) {
  272. String name = beanClass.getName() + "BeanInfo";
  273. try {
  274. return (java.beans.BeanInfo)instantiate(beanClass, name);
  275. } catch (Exception ex) {
  276. // Just drop through
  277. }
  278. // Now try checking if the bean is its own BeanInfo.
  279. try {
  280. if (isSubclass(beanClass, java.beans.BeanInfo.class)) {
  281. return (java.beans.BeanInfo)beanClass.newInstance();
  282. }
  283. } catch (Exception ex) {
  284. // Just drop through
  285. }
  286. // Now try looking for <searchPath>.fooBeanInfo
  287. while (name.indexOf('.') > 0) {
  288. name = name.substring(name.indexOf('.')+1);
  289. }
  290. for (int i = 0; i < searchPath.length; i++) {
  291. try {
  292. String fullName = searchPath[i] + "." + name;
  293. return (java.beans.BeanInfo)instantiate(beanClass, fullName);
  294. } catch (Exception ex) {
  295. // Silently ignore any errors.
  296. }
  297. }
  298. return null;
  299. }
  300. /**
  301. * @return An array of PropertyDescriptors describing the editable
  302. * properties supported by the target bean.
  303. */
  304. private PropertyDescriptor[] getTargetPropertyInfo() throws IntrospectionException {
  305. // Check if the bean has its own BeanInfo that will provide
  306. // explicit information.
  307. PropertyDescriptor[] explicit = null;
  308. if (informant != null) {
  309. explicit = informant.getPropertyDescriptors();
  310. int ix = informant.getDefaultPropertyIndex();
  311. if (ix >= 0 && ix < explicit.length) {
  312. defaultPropertyName = explicit[ix].getName();
  313. }
  314. }
  315. if (explicit == null && superBeanInfo != null) {
  316. // We have no explicit BeanInfo properties. Check with our parent.
  317. PropertyDescriptor supers[] = superBeanInfo.getPropertyDescriptors();
  318. for (int i = 0 ; i < supers.length; i++) {
  319. addProperty(supers[i]);
  320. }
  321. int ix = superBeanInfo.getDefaultPropertyIndex();
  322. if (ix >= 0 && ix < supers.length) {
  323. defaultPropertyName = supers[ix].getName();
  324. }
  325. }
  326. for (int i = 0; i < additionalBeanInfo.length; i++) {
  327. PropertyDescriptor additional[] = additionalBeanInfo[i].getPropertyDescriptors();
  328. if (additional != null) {
  329. for (int j = 0 ; j < additional.length; j++) {
  330. addProperty(additional[j]);
  331. }
  332. }
  333. }
  334. if (explicit != null) {
  335. // Add the explicit informant data to our results.
  336. for (int i = 0 ; i < explicit.length; i++) {
  337. addProperty(explicit[i]);
  338. }
  339. } else {
  340. // Apply some reflection to the current class.
  341. // First get an array of all the public methods at this level
  342. Method methodList[] = getPublicDeclaredMethods(beanClass);
  343. // Now analyze each method.
  344. for (int i = 0; i < methodList.length; i++) {
  345. Method method = methodList[i];
  346. if (method == null) {
  347. continue;
  348. }
  349. // skip static methods.
  350. int mods = method.getModifiers();
  351. if (Modifier.isStatic(mods)) {
  352. continue;
  353. }
  354. String name = method.getName();
  355. Class argTypes[] = method.getParameterTypes();
  356. Class resultType = method.getReturnType();
  357. int argCount = argTypes.length;
  358. PropertyDescriptor pd = null;
  359. try {
  360. if (argCount == 0) {
  361. if (name.startsWith("get")) {
  362. // Simple getter
  363. pd = new PropertyDescriptor(decapitalize(name.substring(3)),
  364. method, null);
  365. } else if (resultType == boolean.class && name.startsWith("is")) {
  366. // Boolean getter
  367. pd = new PropertyDescriptor(decapitalize(name.substring(2)),
  368. method, null);
  369. }
  370. } else if (argCount == 1) {
  371. if (argTypes[0] == int.class && name.startsWith("get")) {
  372. pd = new IndexedPropertyDescriptor(
  373. decapitalize(name.substring(3)),
  374. null, null,
  375. method, null);
  376. } else if (resultType == void.class && name.startsWith("set")) {
  377. // Simple setter
  378. pd = new PropertyDescriptor(decapitalize(name.substring(3)),
  379. null, method);
  380. if (throwsException(method, PropertyVetoException.class)) {
  381. pd.setConstrained(true);
  382. }
  383. }
  384. } else if (argCount == 2) {
  385. if (argTypes[0] == int.class && name.startsWith("set")) {
  386. pd = new IndexedPropertyDescriptor(
  387. decapitalize(name.substring(3)),
  388. null, null,
  389. null, method);
  390. if (throwsException(method, PropertyVetoException.class)) {
  391. pd.setConstrained(true);
  392. }
  393. }
  394. }
  395. } catch (IntrospectionException ex) {
  396. // This happens if a PropertyDescriptor or IndexedPropertyDescriptor
  397. // constructor fins that the method violates details of the deisgn
  398. // pattern, e.g. by having an empty name, or a getter returning
  399. // void , or whatever.
  400. pd = null;
  401. }
  402. if (pd != null) {
  403. // If this class or one of its base classes is a PropertyChange
  404. // source, then we assume that any properties we discover are "bound".
  405. if (propertyChangeSource) {
  406. pd.setBound(true);
  407. }
  408. addProperty(pd);
  409. }
  410. }
  411. }
  412. // Allocate and populate the result array.
  413. PropertyDescriptor result[] = new PropertyDescriptor[properties.size()];
  414. java.util.Enumeration elements = properties.elements();
  415. for (int i = 0; i < result.length; i++) {
  416. result[i] = (PropertyDescriptor)elements.nextElement();
  417. if (defaultPropertyName != null
  418. && defaultPropertyName.equals(result[i].getName())) {
  419. defaultPropertyIndex = i;
  420. }
  421. }
  422. return result;
  423. }
  424. void addProperty(PropertyDescriptor pd) {
  425. String name = pd.getName();
  426. PropertyDescriptor old = (PropertyDescriptor)properties.get(name);
  427. if (old == null) {
  428. properties.put(name, pd);
  429. return;
  430. }
  431. // If the property type has changed, use the new descriptor.
  432. Class opd = old.getPropertyType();
  433. Class npd = pd.getPropertyType();
  434. if (opd != null && npd != null && opd != npd) {
  435. if ((pd.getWriteMethod() != null && pd.getReadMethod() != null) ||
  436. (old.getWriteMethod() == null && pd.getWriteMethod() == null) ||
  437. (old.getReadMethod() == null && pd.getReadMethod() == null)) {
  438. // don't replace a read/write property with a write or
  439. // read only property
  440. properties.put(name, pd);
  441. }
  442. return;
  443. }
  444. PropertyDescriptor composite;
  445. if (old instanceof IndexedPropertyDescriptor ||
  446. pd instanceof IndexedPropertyDescriptor) {
  447. composite = new IndexedPropertyDescriptor(old, pd);
  448. } else {
  449. composite = new PropertyDescriptor(old, pd);
  450. }
  451. properties.put(name, composite);
  452. }
  453. /**
  454. * @return An array of EventSetDescriptors describing the kinds of
  455. * events fired by the target bean.
  456. */
  457. private EventSetDescriptor[] getTargetEventInfo() throws IntrospectionException {
  458. // Check if the bean has its own BeanInfo that will provide
  459. // explicit information.
  460. EventSetDescriptor[] explicit = null;
  461. if (informant != null) {
  462. explicit = informant.getEventSetDescriptors();
  463. int ix = informant.getDefaultEventIndex();
  464. if (ix >= 0 && ix < explicit.length) {
  465. defaultEventName = explicit[ix].getName();
  466. }
  467. }
  468. if (explicit == null && superBeanInfo != null) {
  469. // We have no explicit BeanInfo events. Check with our parent.
  470. EventSetDescriptor supers[] = superBeanInfo.getEventSetDescriptors();
  471. for (int i = 0 ; i < supers.length; i++) {
  472. addEvent(supers[i]);
  473. }
  474. int ix = superBeanInfo.getDefaultEventIndex();
  475. if (ix >= 0 && ix < supers.length) {
  476. defaultEventName = supers[ix].getName();
  477. }
  478. }
  479. for (int i = 0; i < additionalBeanInfo.length; i++) {
  480. EventSetDescriptor additional[] = additionalBeanInfo[i].getEventSetDescriptors();
  481. if (additional != null) {
  482. for (int j = 0 ; j < additional.length; j++) {
  483. addEvent(additional[j]);
  484. }
  485. }
  486. }
  487. if (explicit != null) {
  488. // Add the explicit informant data to our results.
  489. for (int i = 0 ; i < explicit.length; i++) {
  490. addEvent(explicit[i]);
  491. }
  492. } else {
  493. // Apply some reflection to the current class.
  494. // Get an array of all the public beans methods at this level
  495. Method methodList[] = getPublicDeclaredMethods(beanClass);
  496. // Find all suitable "add" and "remove" methods.
  497. java.util.Hashtable adds = new java.util.Hashtable();
  498. java.util.Hashtable removes = new java.util.Hashtable();
  499. for (int i = 0; i < methodList.length; i++) {
  500. Method method = methodList[i];
  501. if (method == null) {
  502. continue;
  503. }
  504. // skip static methods.
  505. int mods = method.getModifiers();
  506. if (Modifier.isStatic(mods)) {
  507. continue;
  508. }
  509. String name = method.getName();
  510. Class argTypes[] = method.getParameterTypes();
  511. Class resultType = method.getReturnType();
  512. if (name.startsWith("add") && argTypes.length == 1 &&
  513. resultType == Void.TYPE) {
  514. String compound = name.substring(3) + ":" + argTypes[0];
  515. adds.put(compound, method);
  516. } else if (name.startsWith("remove") && argTypes.length == 1 &&
  517. resultType == Void.TYPE) {
  518. String compound = name.substring(6) + ":" + argTypes[0];
  519. removes.put(compound, method);
  520. }
  521. }
  522. // Now look for matching addFooListener+removeFooListener pairs.
  523. java.util.Enumeration keys = adds.keys();
  524. String beanClassName = beanClass.getName();
  525. while (keys.hasMoreElements()) {
  526. String compound = (String) keys.nextElement();
  527. // Skip any "add" which doesn't have a matching "remove".
  528. if (removes.get(compound) == null) {
  529. continue;
  530. }
  531. // Method name has to end in "Listener"
  532. if (compound.indexOf("Listener:") <= 0) {
  533. continue;
  534. }
  535. String listenerName = compound.substring(0, compound.indexOf(':'));
  536. String eventName = decapitalize(listenerName.substring(0, listenerName.length()-8));
  537. Method addMethod = (Method)adds.get(compound);
  538. Method removeMethod = (Method)removes.get(compound);
  539. Class argType = addMethod.getParameterTypes()[0];
  540. // Check if the argument type is a subtype of EventListener
  541. if (!Introspector.isSubclass(argType, eventListenerType)) {
  542. continue;
  543. }
  544. // generate a list of Method objects for each of the target methods:
  545. Method allMethods[] = argType.getMethods();
  546. int count = 0;
  547. for (int i = 0; i < allMethods.length; i++) {
  548. if (isEventHandler(allMethods[i])) {
  549. count++;
  550. } else {
  551. allMethods[i] = null;
  552. }
  553. }
  554. Method methods[] = new Method[count];
  555. int j = 0;
  556. for (int i = 0; i < allMethods.length; i++) {
  557. if (allMethods[i] != null) {
  558. methods[j++] = allMethods[i];
  559. }
  560. }
  561. EventSetDescriptor esd = new EventSetDescriptor(eventName, argType,
  562. methods, addMethod, removeMethod);
  563. // If the adder method throws the TooManyListenersException then it
  564. // is a Unicast event source.
  565. if (throwsException(addMethod,
  566. java.util.TooManyListenersException.class)) {
  567. esd.setUnicast(true);
  568. }
  569. addEvent(esd);
  570. }
  571. }
  572. // Allocate and populate the result array.
  573. EventSetDescriptor result[] = new EventSetDescriptor[events.size()];
  574. java.util.Enumeration elements = events.elements();
  575. for (int i = 0; i < result.length; i++) {
  576. result[i] = (EventSetDescriptor)elements.nextElement();
  577. if (defaultEventName != null
  578. && defaultEventName.equals(result[i].getName())) {
  579. defaultEventIndex = i;
  580. }
  581. }
  582. return result;
  583. }
  584. void addEvent(EventSetDescriptor esd) {
  585. String key = esd.getName() + esd.getListenerType();
  586. if (esd.getName().equals("propertyChange")) {
  587. propertyChangeSource = true;
  588. }
  589. EventSetDescriptor old = (EventSetDescriptor)events.get(key);
  590. if (old == null) {
  591. events.put(key, esd);
  592. return;
  593. }
  594. EventSetDescriptor composite = new EventSetDescriptor(old, esd);
  595. events.put(key, composite);
  596. }
  597. /**
  598. * @return An array of MethodDescriptors describing the private
  599. * methods supported by the target bean.
  600. */
  601. private MethodDescriptor[] getTargetMethodInfo() throws IntrospectionException {
  602. // Check if the bean has its own BeanInfo that will provide
  603. // explicit information.
  604. MethodDescriptor[] explicit = null;
  605. if (informant != null) {
  606. explicit = informant.getMethodDescriptors();
  607. }
  608. if (explicit == null && superBeanInfo != null) {
  609. // We have no explicit BeanInfo methods. Check with our parent.
  610. MethodDescriptor supers[] = superBeanInfo.getMethodDescriptors();
  611. for (int i = 0 ; i < supers.length; i++) {
  612. addMethod(supers[i]);
  613. }
  614. }
  615. for (int i = 0; i < additionalBeanInfo.length; i++) {
  616. MethodDescriptor additional[] = additionalBeanInfo[i].getMethodDescriptors();
  617. if (additional != null) {
  618. for (int j = 0 ; j < additional.length; j++) {
  619. addMethod(additional[j]);
  620. }
  621. }
  622. }
  623. if (explicit != null) {
  624. // Add the explicit informant data to our results.
  625. for (int i = 0 ; i < explicit.length; i++) {
  626. addMethod(explicit[i]);
  627. }
  628. } else {
  629. // Apply some reflection to the current class.
  630. // First get an array of all the beans methods at this level
  631. Method methodList[] = getPublicDeclaredMethods(beanClass);
  632. // Now analyze each method.
  633. for (int i = 0; i < methodList.length; i++) {
  634. Method method = methodList[i];
  635. if (method == null) {
  636. continue;
  637. }
  638. MethodDescriptor md = new MethodDescriptor(method);
  639. addMethod(md);
  640. }
  641. }
  642. // Allocate and populate the result array.
  643. MethodDescriptor result[] = new MethodDescriptor[methods.size()];
  644. java.util.Enumeration elements = methods.elements();
  645. for (int i = 0; i < result.length; i++) {
  646. result[i] = (MethodDescriptor)elements.nextElement();
  647. }
  648. return result;
  649. }
  650. private void addMethod(MethodDescriptor md) {
  651. // We have to be careful here to distinguish method by both name
  652. // and argument lists.
  653. // This method gets called a *lot, so we try to be efficient.
  654. String name = md.getMethod().getName();
  655. MethodDescriptor old = (MethodDescriptor)methods.get(name);
  656. if (old == null) {
  657. // This is the common case.
  658. methods.put(name, md);
  659. return;
  660. }
  661. // We have a collision on method names. This is rare.
  662. // Check if old and md have the same type.
  663. Class p1[] = md.getMethod().getParameterTypes();
  664. Class p2[] = old.getMethod().getParameterTypes();
  665. boolean match = false;
  666. if (p1.length == p2.length) {
  667. match = true;
  668. for (int i = 0; i < p1.length; i++) {
  669. if (p1[i] != p2[i]) {
  670. match = false;
  671. break;
  672. }
  673. }
  674. }
  675. if (match) {
  676. MethodDescriptor composite = new MethodDescriptor(old, md);
  677. methods.put(name, composite);
  678. return;
  679. }
  680. // We have a collision on method names with different type signatures.
  681. // This is very rare.
  682. String longKey = makeQualifiedMethodName(md);
  683. old = (MethodDescriptor)methods.get(longKey);
  684. if (old == null) {
  685. methods.put(longKey, md);
  686. return;
  687. }
  688. MethodDescriptor composite = new MethodDescriptor(old, md);
  689. methods.put(longKey, composite);
  690. }
  691. private String makeQualifiedMethodName(MethodDescriptor md) {
  692. Method m = md.getMethod();
  693. StringBuffer sb = new StringBuffer();
  694. sb.append(m.getName());
  695. sb.append("=");
  696. Class params[] = m.getParameterTypes();
  697. for (int i = 0; i < params.length; i++) {
  698. sb.append(":");
  699. sb.append(params[i].getName());
  700. }
  701. return sb.toString();
  702. }
  703. private int getTargetDefaultEventIndex() {
  704. return defaultEventIndex;
  705. }
  706. private int getTargetDefaultPropertyIndex() {
  707. return defaultPropertyIndex;
  708. }
  709. private BeanDescriptor getTargetBeanDescriptor() throws IntrospectionException {
  710. // Use explicit info, if available,
  711. if (informant != null) {
  712. BeanDescriptor bd = informant.getBeanDescriptor();
  713. if (bd != null) {
  714. return (bd);
  715. }
  716. }
  717. // OK, fabricate a default BeanDescriptor.
  718. return (new BeanDescriptor(beanClass));
  719. }
  720. private boolean isEventHandler(Method m) throws IntrospectionException {
  721. // We assume that a method is an event handler if it has a single
  722. // argument, whose type inherit from java.util.Event.
  723. try {
  724. Class argTypes[] = m.getParameterTypes();
  725. if (argTypes.length != 1) {
  726. return false;
  727. }
  728. if (isSubclass(argTypes[0], java.util.EventObject.class)) {
  729. return true;
  730. } else {
  731. return false;
  732. }
  733. } catch (Exception ex) {
  734. throw new IntrospectionException("Unexpected reflection exception: " + ex);
  735. }
  736. }
  737. /*
  738. * Internal method to return *public* methods within a class.
  739. */
  740. private static synchronized Method[] getPublicDeclaredMethods(Class clz) {
  741. // Looking up Class.getDeclaredMethods is relatively expensive,
  742. // so we cache the results.
  743. final Class fclz = clz;
  744. Method[] result = (Method[])declaredMethodCache.get(fclz);
  745. if (result != null) {
  746. return result;
  747. }
  748. // We have to raise privilege for getDeclaredMethods
  749. result = (Method[]) AccessController.doPrivileged(new PrivilegedAction() {
  750. public Object run() {
  751. return fclz.getDeclaredMethods();
  752. }
  753. });
  754. // Null out any non-public methods.
  755. for (int i = 0; i < result.length; i++) {
  756. Method method = result[i];
  757. int mods = method.getModifiers();
  758. if (!Modifier.isPublic(mods)) {
  759. result[i] = null;
  760. }
  761. }
  762. // Add it to the cache.
  763. declaredMethodCache.put(clz, result);
  764. return result;
  765. }
  766. //======================================================================
  767. // Package private support methods.
  768. //======================================================================
  769. /**
  770. * Internal support for finding a target methodName on a given class.
  771. */
  772. private static Method internalFindMethod(Class start, String methodName,
  773. int argCount) {
  774. // For overriden methods we need to find the most derived version.
  775. // So we start with the given class and walk up the superclass chain.
  776. for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
  777. Method methods[] = getPublicDeclaredMethods(cl);
  778. for (int i = 0; i < methods.length; i++) {
  779. Method method = methods[i];
  780. if (method == null) {
  781. continue;
  782. }
  783. // skip static methods.
  784. int mods = method.getModifiers();
  785. if (Modifier.isStatic(mods)) {
  786. continue;
  787. }
  788. if (method.getName().equals(methodName) &&
  789. method.getParameterTypes().length == argCount) {
  790. return method;
  791. }
  792. }
  793. }
  794. // Now check any inherited interfaces. This is necessary both when
  795. // the argument class is itself an interface, and when the argument
  796. // class is an abstract class.
  797. Class ifcs[] = start.getInterfaces();
  798. for (int i = 0 ; i < ifcs.length; i++) {
  799. Method m = internalFindMethod(ifcs[i], methodName, argCount);
  800. if (m != null) {
  801. return m;
  802. }
  803. }
  804. return null;
  805. }
  806. /**
  807. * Internal support for finding a target methodName with a given
  808. * parameter list on a given class.
  809. */
  810. private static Method internalFindMethod(Class start, String methodName,
  811. int argCount, Class args[]) {
  812. // For overriden methods we need to find the most derived version.
  813. // So we start with the given class and walk up the superclass chain.
  814. for (Class cl = start; cl != null; cl = cl.getSuperclass()) {
  815. Method methods[] = getPublicDeclaredMethods(cl);
  816. for (int i = 0; i < methods.length; i++) {
  817. Method method = methods[i];
  818. if (method == null) {
  819. continue;
  820. }
  821. // skip static methods.
  822. int mods = method.getModifiers();
  823. if (Modifier.isStatic(mods)) {
  824. continue;
  825. }
  826. // make sure method signature matches.
  827. Class params[] = method.getParameterTypes();
  828. if (method.getName().equals(methodName) &&
  829. params.length == argCount) {
  830. boolean different = false;
  831. if (argCount > 0) {
  832. for (int j = 0; j < argCount; j++) {
  833. if (params[j] != args[j]) {
  834. different = true;
  835. continue;
  836. }
  837. }
  838. if (different) {
  839. continue;
  840. }
  841. }
  842. return method;
  843. }
  844. }
  845. }
  846. // Now check any inherited interfaces. This is necessary both when
  847. // the argument class is itself an interface, and when the argument
  848. // class is an abstract class.
  849. Class ifcs[] = start.getInterfaces();
  850. for (int i = 0 ; i < ifcs.length; i++) {
  851. Method m = internalFindMethod(ifcs[i], methodName, argCount);
  852. if (m != null) {
  853. return m;
  854. }
  855. }
  856. return null;
  857. }
  858. /**
  859. * Find a target methodName on a given class.
  860. */
  861. static Method findMethod(Class cls, String methodName, int argCount)
  862. throws IntrospectionException {
  863. if (methodName == null) {
  864. return null;
  865. }
  866. Method m = internalFindMethod(cls, methodName, argCount);
  867. if (m != null ) {
  868. return m;
  869. }
  870. // We failed to find a suitable method
  871. throw new IntrospectionException("No method \"" + methodName +
  872. "\" with " + argCount + " arg(s)");
  873. }
  874. /**
  875. * Find a target methodName with specific parameter list on a given class.
  876. */
  877. static Method findMethod(Class cls, String methodName, int argCount,
  878. Class args[]) throws IntrospectionException {
  879. if (methodName == null) {
  880. return null;
  881. }
  882. Method m = internalFindMethod(cls, methodName, argCount, args);
  883. if (m != null ) {
  884. return m;
  885. }
  886. // We failed to find a suitable method
  887. throw new IntrospectionException("No method \"" + methodName +
  888. "\" with " + argCount + " arg(s) of matching types.");
  889. }
  890. /**
  891. * Return true if class a is either equivalent to class b, or
  892. * if class a is a subclass of class b, i.e. if a either "extends"
  893. * or "implements" b.
  894. * Note tht either or both "Class" objects may represent interfaces.
  895. */
  896. static boolean isSubclass(Class a, Class b) {
  897. // We rely on the fact that for any given java class or
  898. // primtitive type there is a unqiue Class object, so
  899. // we can use object equivalence in the comparisons.
  900. if (a == b) {
  901. return true;
  902. }
  903. if (a == null || b == null) {
  904. return false;
  905. }
  906. for (Class x = a; x != null; x = x.getSuperclass()) {
  907. if (x == b) {
  908. return true;
  909. }
  910. if (b.isInterface()) {
  911. Class interfaces[] = x.getInterfaces();
  912. for (int i = 0; i < interfaces.length; i++) {
  913. if (isSubclass(interfaces[i], b)) {
  914. return true;
  915. }
  916. }
  917. }
  918. }
  919. return false;
  920. }
  921. /**
  922. * Return true iff the given method throws the given exception.
  923. */
  924. private boolean throwsException(Method method, Class exception) {
  925. Class exs[] = method.getExceptionTypes();
  926. for (int i = 0; i < exs.length; i++) {
  927. if (exs[i] == exception) {
  928. return true;
  929. }
  930. }
  931. return false;
  932. }
  933. /**
  934. * Try to create an instance of a named class.
  935. * First try the classloader of "sibling", then try the system
  936. * classloader.
  937. */
  938. static Object instantiate(Class sibling, String className)
  939. throws InstantiationException, IllegalAccessException,
  940. ClassNotFoundException {
  941. // First check with sibling's classloader (if any).
  942. ClassLoader cl = sibling.getClassLoader();
  943. if (cl != null) {
  944. try {
  945. Class cls = cl.loadClass(className);
  946. return cls.newInstance();
  947. } catch (Exception ex) {
  948. // Just drop through and try the system classloader.
  949. }
  950. }
  951. // Now try the system classloader.
  952. try {
  953. cl = ClassLoader.getSystemClassLoader();
  954. if (cl != null) {
  955. Class cls = cl.loadClass(className);
  956. return cls.newInstance();
  957. }
  958. } catch (Exception ex) {
  959. // We're not allowed to access the system class loader or
  960. // the class creation failed.
  961. // Drop through.
  962. }
  963. // Now try the bootstrap classloader.
  964. try {
  965. Class cls = Class.forName(className);
  966. return cls.newInstance();
  967. } catch (Exception ex) {
  968. // We're not allowed to access the system class loader or
  969. // the class creation failed.
  970. // Drop through.
  971. }
  972. // Use the classloader from the current Thread.
  973. cl = Thread.currentThread().getContextClassLoader();
  974. Class cls = cl.loadClass(className);
  975. return cls.newInstance();
  976. }
  977. //======================================================================
  978. private BeanInfo informant;
  979. private boolean propertyChangeSource = false;
  980. private Class beanClass;
  981. private BeanInfo superBeanInfo;
  982. private BeanInfo additionalBeanInfo[];
  983. private static java.util.Hashtable beanInfoCache = new java.util.Hashtable();
  984. private static Class eventListenerType = java.util.EventListener.class;
  985. private String defaultEventName;
  986. private String defaultPropertyName;
  987. private int defaultEventIndex = -1;
  988. private int defaultPropertyIndex = -1;
  989. // Methods maps from Method objects to MethodDescriptors
  990. private java.util.Hashtable methods = new java.util.Hashtable();
  991. // Cache of Class.getDeclaredMethods:
  992. private static java.util.Hashtable declaredMethodCache = new java.util.Hashtable();
  993. // properties maps from String names to PropertyDescriptors
  994. private java.util.Hashtable properties = new java.util.Hashtable();
  995. // events maps from String names to EventSetDescriptors
  996. private java.util.Hashtable events = new java.util.Hashtable();
  997. private static String[] searchPath = { "sun.beans.infos" };
  998. }
  999. //===========================================================================
  1000. /**
  1001. * Package private implementation support class for Introspector's
  1002. * internal use.
  1003. */
  1004. class GenericBeanInfo extends SimpleBeanInfo {
  1005. public GenericBeanInfo(BeanDescriptor beanDescriptor,
  1006. EventSetDescriptor[] events, int defaultEvent,
  1007. PropertyDescriptor[] properties, int defaultProperty,
  1008. MethodDescriptor[] methods, BeanInfo targetBeanInfo) {
  1009. this.beanDescriptor = beanDescriptor;
  1010. this.events = events;
  1011. this.defaultEvent = defaultEvent;
  1012. this.properties = properties;
  1013. this.defaultProperty = defaultProperty;
  1014. this.methods = methods;
  1015. this.targetBeanInfo = targetBeanInfo;
  1016. }
  1017. /**
  1018. * Package-private dup constructor
  1019. * This must isolate the new object from any changes to the old object.
  1020. */
  1021. GenericBeanInfo(GenericBeanInfo old) {
  1022. beanDescriptor = new BeanDescriptor(old.beanDescriptor);
  1023. if (old.events != null) {
  1024. int len = old.events.length;
  1025. events = new EventSetDescriptor[len];
  1026. for (int i = 0; i < len; i++) {
  1027. events[i] = new EventSetDescriptor(old.events[i]);
  1028. }
  1029. }
  1030. defaultEvent = old.defaultEvent;
  1031. if (old.properties != null) {
  1032. int len = old.properties.length;
  1033. properties = new PropertyDescriptor[len];
  1034. for (int i = 0; i < len; i++) {
  1035. PropertyDescriptor oldp = old.properties[i];
  1036. if (oldp instanceof IndexedPropertyDescriptor) {
  1037. properties[i] = new IndexedPropertyDescriptor(
  1038. (IndexedPropertyDescriptor) oldp);
  1039. } else {
  1040. properties[i] = new PropertyDescriptor(oldp);
  1041. }
  1042. }
  1043. }
  1044. defaultProperty = old.defaultProperty;
  1045. if (old.methods != null) {
  1046. int len = old.methods.length;
  1047. methods = new MethodDescriptor[len];
  1048. for (int i = 0; i < len; i++) {
  1049. methods[i] = new MethodDescriptor(old.methods[i]);
  1050. }
  1051. }
  1052. targetBeanInfo = old.targetBeanInfo;
  1053. }
  1054. public PropertyDescriptor[] getPropertyDescriptors() {
  1055. return properties;
  1056. }
  1057. public int getDefaultPropertyIndex() {
  1058. return defaultProperty;
  1059. }
  1060. public EventSetDescriptor[] getEventSetDescriptors() {
  1061. return events;
  1062. }
  1063. public int getDefaultEventIndex() {
  1064. return defaultEvent;
  1065. }
  1066. public MethodDescriptor[] getMethodDescriptors() {
  1067. return methods;
  1068. }
  1069. public BeanDescriptor getBeanDescriptor() {
  1070. return beanDescriptor;
  1071. }
  1072. public java.awt.Image getIcon(int iconKind) {
  1073. if (targetBeanInfo != null) {
  1074. return targetBeanInfo.getIcon(iconKind);
  1075. }
  1076. return super.getIcon(iconKind);
  1077. }
  1078. private BeanDescriptor beanDescriptor;
  1079. private EventSetDescriptor[] events;
  1080. private int defaultEvent;
  1081. private PropertyDescriptor[] properties;
  1082. private int defaultProperty;
  1083. private MethodDescriptor[] methods;
  1084. private BeanInfo targetBeanInfo;
  1085. }