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