Frames | No Frames |
1: /* java.beans.Introspector 2: Copyright (C) 1998, 2002, 2003 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package java.beans; 40: 41: import gnu.java.beans.BeanInfoEmbryo; 42: import gnu.java.beans.ExplicitBeanInfo; 43: import gnu.java.beans.IntrospectionIncubator; 44: import gnu.java.lang.ClassHelper; 45: 46: import java.util.Hashtable; 47: import java.util.Vector; 48: 49: /** 50: * Introspector is the class that does the bulk of the 51: * design-time work in Java Beans. Every class must have 52: * a BeanInfo in order for an RAD tool to use it; but, as 53: * promised, you don't have to write the BeanInfo class 54: * yourself if you don't want to. All you have to do is 55: * call getBeanInfo() in the Introspector and it will use 56: * standard JavaBeans-defined method signatures to 57: * determine the information about your class.<P> 58: * 59: * Don't worry about it too much, though: you can provide 60: * JavaBeans with as much customized information as you 61: * want, or as little as you want, using the BeanInfo 62: * interface (see BeanInfo for details).<P> 63: * 64: * <STRONG>Order of Operations</STRONG><P> 65: * 66: * When you call getBeanInfo(class c), the Introspector 67: * first searches for BeanInfo class to see if you 68: * provided any explicit information. It searches for a 69: * class named <bean class name>BeanInfo in different 70: * packages, first searching the bean class's package 71: * and then moving on to search the beanInfoSearchPath.<P> 72: * 73: * If it does not find a BeanInfo class, it acts as though 74: * it had found a BeanInfo class returning null from all 75: * methods (meaning it should discover everything through 76: * Introspection). If it does, then it takes the 77: * information it finds in the BeanInfo class to be 78: * canonical (that is, the information speaks for its 79: * class as well as all superclasses).<P> 80: * 81: * When it has introspected the class, calls 82: * getBeanInfo(c.getSuperclass) and adds that information 83: * to the information it has, not adding to any information 84: * it already has that is canonical.<P> 85: * 86: * <STRONG>Introspection Design Patterns</STRONG><P> 87: * 88: * When the Introspector goes in to read the class, it 89: * follows a well-defined order in order to not leave any 90: * methods unaccounted for. Its job is to step over all 91: * of the public methods in a class and determine whether 92: * they are part of a property, an event, or a method (in 93: * that order). 94: * 95: * 96: * <STRONG>Properties:</STRONG><P> 97: * 98: * <OL> 99: * <LI>If there is a <CODE>public boolean isXXX()</CODE> 100: * method, then XXX is a read-only boolean property. 101: * <CODE>boolean getXXX()</CODE> may be supplied in 102: * addition to this method, although isXXX() is the 103: * one that will be used in this case and getXXX() 104: * will be ignored. If there is a 105: * <CODE>public void setXXX(boolean)</CODE> method, 106: * it is part of this group and makes it a read-write 107: * property.</LI> 108: * <LI>If there is a 109: * <CODE>public <type> getXXX(int)</CODE> 110: * method, then XXX is a read-only indexed property of 111: * type <type>. If there is a 112: * <CODE>public void setXXX(int,<type>)</CODE> 113: * method, then it is a read-write indexed property of 114: * type <type>. There may also be a 115: * <CODE>public <type>[] getXXX()</CODE> and a 116: * <CODE>public void setXXX(<type>)</CODE> 117: * method as well.</LI> 118: * <LI>If there is a 119: * <CODE>public void setXXX(int,<type>)</CODE> 120: * method, then it is a write-only indexed property of 121: * type <type>. There may also be a 122: * <CODE>public <type>[] getXXX()</CODE> and a 123: * <CODE>public void setXXX(<type>)</CODE> 124: * method as well.</LI> 125: * <LI>If there is a 126: * <CODE>public <type> getXXX()</CODE> method, 127: * then XXX is a read-only property of type 128: * <type>. If there is a 129: * <CODE>public void setXXX(<type>)</CODE> 130: * method, then it will be used for the property and 131: * the property will be considered read-write.</LI> 132: * <LI>If there is a 133: * <CODE>public void setXXX(<type>)</CODE> 134: * method, then as long as XXX is not already used as 135: * the name of a property, XXX is assumed to be a 136: * write-only property of type <type>.</LI> 137: * <LI>In all of the above cases, if the setXXX() method 138: * throws <CODE>PropertyVetoException</CODE>, then the 139: * property in question is assumed to be constrained. 140: * No properties are ever assumed to be bound 141: * (<STRONG>Spec Note:</STRONG> this is not in the 142: * spec, it just makes sense). See PropertyDescriptor 143: * for a description of bound and constrained 144: * properties.</LI> 145: * </OL> 146: * 147: * <STRONG>Events:</STRONG><P> 148: * 149: * If there is a pair of methods, 150: * <CODE>public void addXXX(<type>)</CODE> and 151: * <CODE>public void removeXXX(<type>)</CODE>, where 152: * <type> is a descendant of 153: * <CODE>java.util.EventListener</CODE>, then the pair of 154: * methods imply that this Bean will fire events to 155: * listeners of type <type>.<P> 156: * 157: * If the addXXX() method throws 158: * <CODE>java.util.TooManyListenersException</CODE>, then 159: * the event set is assumed to be <EM>unicast</EM>. See 160: * EventSetDescriptor for a discussion of unicast event 161: * sets.<P> 162: * 163: * <STRONG>Spec Note:</STRONG> the spec seems to say that 164: * the listener type's classname must be equal to the XXX 165: * part of addXXX() and removeXXX(), but that is not the 166: * case in Sun's implementation, so I am assuming it is 167: * not the case in general.<P> 168: * 169: * <STRONG>Methods:</STRONG><P> 170: * 171: * Any public methods (including those which were used 172: * for Properties or Events) are used as Methods. 173: * 174: * @author John Keiser 175: * @since JDK1.1 176: * @see java.beans.BeanInfo 177: */ 178: public class Introspector { 179: 180: public static final int USE_ALL_BEANINFO = 1; 181: public static final int IGNORE_IMMEDIATE_BEANINFO = 2; 182: public static final int IGNORE_ALL_BEANINFO = 3; 183: 184: static String[] beanInfoSearchPath = {"gnu.java.beans.info"}; 185: static Hashtable beanInfoCache = new Hashtable(); 186: 187: private Introspector() {} 188: 189: /** 190: * Get the BeanInfo for class <CODE>beanClass</CODE>, 191: * first by looking for explicit information, next by 192: * using standard design patterns to determine 193: * information about the class. 194: * 195: * @param beanClass the class to get BeanInfo about. 196: * @return the BeanInfo object representing the class. 197: */ 198: public static BeanInfo getBeanInfo(Class beanClass) 199: throws IntrospectionException 200: { 201: BeanInfo cachedInfo; 202: synchronized(beanClass) 203: { 204: cachedInfo = (BeanInfo)beanInfoCache.get(beanClass); 205: if(cachedInfo != null) 206: { 207: return cachedInfo; 208: } 209: cachedInfo = getBeanInfo(beanClass,null); 210: beanInfoCache.put(beanClass,cachedInfo); 211: return cachedInfo; 212: } 213: } 214: 215: /** 216: * Returns a {@BeanInfo} instance for the given Bean class where a flag 217: * controls the usage of explicit BeanInfo class to retrieve that 218: * information. 219: * 220: * <p>You have three options:</p> 221: * <p>With {@link #USE_ALL_BEANINFO} the result is the same as 222: * {@link #getBeanInfo(Class)}.</p> 223: * 224: * <p>Calling the method with <code>flag</code> set to 225: * {@link #IGNORE_IMMEDIATE_BEANINFO} will let it use all 226: * explicit BeanInfo classes for the beans superclasses 227: * but not for the bean class itself. Furthermore eventset, 228: * property and method information is retrieved by introspection 229: * if the explicit <code>BeanInfos</code> did not provide such data 230: * (ie. return <code>null</code> on {@link BeanInfo.getMethodDescriptors}, 231: * {@link BeanInfo.getEventSetDescriptors} and 232: * {@link BeanInfo.getPropertyDescriptors}.) 233: * </p> 234: * 235: * <p>When the method is called with <code>flag</code< set to 236: * {@link #IGNORE_ALL_BEANINFO} all the bean data is retrieved 237: * by inspecting the class.</p> 238: * 239: * <p>Note: Any unknown value for <code>flag</code> is interpreted 240: * as {@link #IGNORE_ALL_BEANINFO}</p>. 241: * 242: * @param beanClass The class whose BeanInfo should be returned. 243: * @param flag Controls the usage of explicit <code>BeanInfo</code> classes. 244: * @return A BeanInfo object describing the class. 245: * @throws IntrospectionException If something goes wrong while retrieving 246: * the bean data. 247: */ 248: public static BeanInfo getBeanInfo(Class beanClass, int flag) 249: throws IntrospectionException 250: { 251: IntrospectionIncubator ii; 252: BeanInfoEmbryo infoEmbryo; 253: 254: switch(flag) 255: { 256: case USE_ALL_BEANINFO: 257: return getBeanInfo(beanClass); 258: case IGNORE_IMMEDIATE_BEANINFO: 259: Class superclass = beanClass.getSuperclass(); 260: ExplicitInfo explicit = new ExplicitInfo(superclass, null); 261: 262: ii = new IntrospectionIncubator(); 263: if (explicit.explicitEventSetDescriptors != null) 264: ii.setEventStopClass(superclass); 265: 266: if (explicit.explicitMethodDescriptors != null) 267: ii.setMethodStopClass(superclass); 268: 269: if (explicit.explicitPropertyDescriptors != null) 270: ii.setPropertyStopClass(superclass); 271: 272: ii.addMethods(beanClass.getMethods()); 273: 274: infoEmbryo = ii.getBeanInfoEmbryo(); 275: merge(infoEmbryo, explicit); 276: 277: infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null)); 278: 279: return infoEmbryo.getBeanInfo(); 280: case IGNORE_ALL_BEANINFO: 281: default: 282: ii = new IntrospectionIncubator(); 283: ii.addMethods(beanClass.getMethods()); 284: infoEmbryo = ii.getBeanInfoEmbryo(); 285: infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null)); 286: 287: return infoEmbryo.getBeanInfo(); 288: } 289: } 290: 291: /** 292: * Flush all of the Introspector's internal caches. 293: * 294: * @since 1.2 295: */ 296: public static void flushCaches() 297: { 298: beanInfoCache.clear(); 299: 300: // Clears all the intermediate ExplicitInfo instances which 301: // have been created. 302: // This makes sure we have to retrieve stuff like BeanDescriptors 303: // again. (Remember that FeatureDescriptor can be modified by the user.) 304: ExplicitInfo.flushCaches(); 305: } 306: 307: /** 308: * Flush the Introspector's internal cached information for a given 309: * class. 310: * 311: * @param clz the class to be flushed. 312: * @throws NullPointerException if clz is null. 313: * @since 1.2 314: */ 315: public static void flushFromCaches(Class clz) 316: { 317: synchronized (clz) 318: { 319: beanInfoCache.remove(clz); 320: } 321: } 322: 323: /** Adds all explicity given bean info data to the introspected 324: * data. 325: * 326: * @param infoEmbryo Bean info data retrieved by introspection. 327: * @param explicit Bean info data retrieved by BeanInfo classes. 328: */ 329: private static void merge(BeanInfoEmbryo infoEmbryo, ExplicitInfo explicit) 330: { 331: PropertyDescriptor[] p = explicit.explicitPropertyDescriptors; 332: if(p!=null) 333: { 334: for(int i=0;i<p.length;i++) 335: { 336: if(!infoEmbryo.hasProperty(p[i])) 337: { 338: infoEmbryo.addProperty(p[i]); 339: } 340: } 341: 342: // -1 should be used to denote a missing default property but 343: // for robustness reasons any value below zero is discarded. 344: // Not doing so would let Classpath fail where the JDK succeeds. 345: if(explicit.defaultProperty > -1) 346: { 347: infoEmbryo.setDefaultPropertyName(p[explicit.defaultProperty].getName()); 348: } 349: } 350: EventSetDescriptor[] e = explicit.explicitEventSetDescriptors; 351: if(e!=null) 352: { 353: for(int i=0;i<e.length;i++) 354: { 355: if(!infoEmbryo.hasEvent(e[i])) 356: { 357: infoEmbryo.addEvent(e[i]); 358: } 359: } 360: 361: // -1 should be used to denote a missing default event but 362: // for robustness reasons any value below zero is discarded. 363: // Not doing so would let Classpath fail where the JDK succeeds. 364: if(explicit.defaultEvent > -1) 365: { 366: infoEmbryo.setDefaultEventName(e[explicit.defaultEvent].getName()); 367: } 368: } 369: MethodDescriptor[] m = explicit.explicitMethodDescriptors; 370: if(m!=null) 371: { 372: for(int i=0;i<m.length;i++) 373: { 374: if(!infoEmbryo.hasMethod(m[i])) 375: { 376: infoEmbryo.addMethod(m[i]); 377: } 378: } 379: } 380: 381: infoEmbryo.setAdditionalBeanInfo(explicit.explicitBeanInfo); 382: infoEmbryo.setIcons(explicit.im); 383: 384: } 385: 386: /** 387: * Get the BeanInfo for class <CODE>beanClass</CODE>, 388: * first by looking for explicit information, next by 389: * using standard design patterns to determine 390: * information about the class. It crawls up the 391: * inheritance tree until it hits <CODE>topClass</CODE>. 392: * 393: * @param beanClass the Bean class. 394: * @param stopClass the class to stop at. 395: * @return the BeanInfo object representing the class. 396: */ 397: public static BeanInfo getBeanInfo(Class beanClass, Class stopClass) 398: throws IntrospectionException 399: { 400: ExplicitInfo explicit = new ExplicitInfo(beanClass, stopClass); 401: 402: IntrospectionIncubator ii = new IntrospectionIncubator(); 403: ii.setPropertyStopClass(explicit.propertyStopClass); 404: ii.setEventStopClass(explicit.eventStopClass); 405: ii.setMethodStopClass(explicit.methodStopClass); 406: ii.addMethods(beanClass.getMethods()); 407: 408: BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo(); 409: 410: merge(currentInfo, explicit); 411: 412: // Sets the info's BeanDescriptor to the one we extracted from the 413: // explicit BeanInfo instance(s) if they contained one. Otherwise we 414: // create the BeanDescriptor from scratch. 415: // Note: We do not create a copy the retrieved BeanDescriptor which will allow 416: // the user to modify the instance while it is cached. However this is how 417: // the RI does it. 418: currentInfo.setBeanDescriptor( 419: (explicit.explicitBeanDescriptor == null ? 420: new BeanDescriptor(beanClass, null) : 421: explicit.explicitBeanDescriptor)); 422: return currentInfo.getBeanInfo(); 423: } 424: 425: /** 426: * Get the search path for BeanInfo classes. 427: * 428: * @return the BeanInfo search path. 429: */ 430: public static String[] getBeanInfoSearchPath() 431: { 432: return beanInfoSearchPath; 433: } 434: 435: /** 436: * Set the search path for BeanInfo classes. 437: * @param beanInfoSearchPath the new BeanInfo search 438: * path. 439: */ 440: public static void setBeanInfoSearchPath(String[] beanInfoSearchPath) 441: { 442: Introspector.beanInfoSearchPath = beanInfoSearchPath; 443: } 444: 445: /** 446: * A helper method to convert a name to standard Java 447: * naming conventions: anything with two capitals as the 448: * first two letters remains the same, otherwise the 449: * first letter is decapitalized. URL = URL, I = i, 450: * MyMethod = myMethod. 451: * 452: * @param name the name to decapitalize. 453: * @return the decapitalized name. 454: */ 455: public static String decapitalize(String name) 456: { 457: try 458: { 459: if(!Character.isUpperCase(name.charAt(0))) 460: { 461: return name; 462: } 463: else 464: { 465: try 466: { 467: if(Character.isUpperCase(name.charAt(1))) 468: { 469: return name; 470: } 471: else 472: { 473: char[] c = name.toCharArray(); 474: c[0] = Character.toLowerCase(c[0]); 475: return new String(c); 476: } 477: } 478: catch(StringIndexOutOfBoundsException E) 479: { 480: char[] c = new char[1]; 481: c[0] = Character.toLowerCase(name.charAt(0)); 482: return new String(c); 483: } 484: } 485: } 486: catch(StringIndexOutOfBoundsException E) 487: { 488: return name; 489: } 490: catch(NullPointerException E) 491: { 492: return null; 493: } 494: } 495: 496: static BeanInfo copyBeanInfo(BeanInfo b) 497: { 498: java.awt.Image[] icons = new java.awt.Image[4]; 499: for(int i=1;i<=4;i++) 500: { 501: icons[i-1] = b.getIcon(i); 502: } 503: 504: return new ExplicitBeanInfo(b.getBeanDescriptor(), 505: b.getAdditionalBeanInfo(), 506: b.getPropertyDescriptors(), 507: b.getDefaultPropertyIndex(), 508: b.getEventSetDescriptors(), 509: b.getDefaultEventIndex(), 510: b.getMethodDescriptors(), 511: icons); 512: } 513: } 514: 515: class ExplicitInfo 516: { 517: BeanDescriptor explicitBeanDescriptor; 518: BeanInfo[] explicitBeanInfo; 519: 520: PropertyDescriptor[] explicitPropertyDescriptors; 521: EventSetDescriptor[] explicitEventSetDescriptors; 522: MethodDescriptor[] explicitMethodDescriptors; 523: 524: int defaultProperty; 525: int defaultEvent; 526: 527: java.awt.Image[] im = new java.awt.Image[4]; 528: 529: Class propertyStopClass; 530: Class eventStopClass; 531: Class methodStopClass; 532: 533: static Hashtable explicitBeanInfos = new Hashtable(); 534: static Vector emptyBeanInfos = new Vector(); 535: 536: ExplicitInfo(Class beanClass, Class stopClass) 537: { 538: while(beanClass != null && !beanClass.equals(stopClass)) 539: { 540: 541: BeanInfo explicit = findExplicitBeanInfo(beanClass); 542: 543: 544: if(explicit != null) 545: { 546: 547: if(explicitBeanDescriptor == null) 548: { 549: explicitBeanDescriptor = explicit.getBeanDescriptor(); 550: } 551: 552: if(explicitBeanInfo == null) 553: { 554: explicitBeanInfo = explicit.getAdditionalBeanInfo(); 555: } 556: 557: if(explicitPropertyDescriptors == null) 558: { 559: if(explicit.getPropertyDescriptors() != null) 560: { 561: explicitPropertyDescriptors = explicit.getPropertyDescriptors(); 562: defaultProperty = explicit.getDefaultPropertyIndex(); 563: propertyStopClass = beanClass; 564: } 565: } 566: 567: if(explicitEventSetDescriptors == null) 568: { 569: if(explicit.getEventSetDescriptors() != null) 570: { 571: explicitEventSetDescriptors = explicit.getEventSetDescriptors(); 572: defaultEvent = explicit.getDefaultEventIndex(); 573: eventStopClass = beanClass; 574: } 575: } 576: 577: if(explicitMethodDescriptors == null) 578: { 579: if(explicit.getMethodDescriptors() != null) 580: { 581: explicitMethodDescriptors = explicit.getMethodDescriptors(); 582: methodStopClass = beanClass; 583: } 584: } 585: 586: if(im[0] == null && im[1] == null 587: && im[2] == null && im[3] == null) 588: { 589: im[0] = explicit.getIcon(0); 590: im[1] = explicit.getIcon(1); 591: im[2] = explicit.getIcon(2); 592: im[3] = explicit.getIcon(3); 593: } 594: } 595: beanClass = beanClass.getSuperclass(); 596: } 597: 598: if(propertyStopClass == null) 599: { 600: propertyStopClass = stopClass; 601: } 602: 603: if(eventStopClass == null) 604: { 605: eventStopClass = stopClass; 606: } 607: 608: if(methodStopClass == null) 609: { 610: methodStopClass = stopClass; 611: } 612: } 613: 614: /** Throws away all cached data and makes sure we re-instantiate things 615: * like BeanDescriptors again. 616: */ 617: static void flushCaches() { 618: explicitBeanInfos.clear(); 619: emptyBeanInfos.clear(); 620: } 621: 622: static BeanInfo findExplicitBeanInfo(Class beanClass) 623: { 624: BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass); 625: if(retval != null) 626: { 627: return retval; 628: } 629: else if(emptyBeanInfos.indexOf(beanClass) != -1) 630: { 631: return null; 632: } 633: else 634: { 635: retval = reallyFindExplicitBeanInfo(beanClass); 636: if(retval != null) 637: { 638: explicitBeanInfos.put(beanClass,retval); 639: } 640: else 641: { 642: emptyBeanInfos.addElement(beanClass); 643: } 644: return retval; 645: } 646: } 647: 648: static BeanInfo reallyFindExplicitBeanInfo(Class beanClass) 649: { 650: ClassLoader beanClassLoader = beanClass.getClassLoader(); 651: BeanInfo beanInfo; 652: 653: beanInfo = getBeanInfo(beanClassLoader, beanClass.getName() + "BeanInfo"); 654: if (beanInfo == null) 655: { 656: String newName; 657: newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo"; 658: 659: for(int i = 0; i < Introspector.beanInfoSearchPath.length; i++) 660: { 661: if (Introspector.beanInfoSearchPath[i].equals("")) 662: beanInfo = getBeanInfo(beanClassLoader, newName); 663: else 664: beanInfo = getBeanInfo(beanClassLoader, 665: Introspector.beanInfoSearchPath[i] + "." 666: + newName); 667: 668: // Returns the beanInfo if it exists and the described class matches 669: // the one we searched. 670: if (beanInfo != null && beanInfo.getBeanDescriptor() != null && 671: beanInfo.getBeanDescriptor().getBeanClass() == beanClass) 672: 673: return beanInfo; 674: } 675: } 676: 677: return beanInfo; 678: } 679: 680: /** 681: * Returns an instance of the given class name when it can be loaded 682: * through the given class loader, or null otherwise. 683: */ 684: private static BeanInfo getBeanInfo(ClassLoader cl, String infoName) 685: { 686: try 687: { 688: return (BeanInfo) Class.forName(infoName, true, cl).newInstance(); 689: } 690: catch (ClassNotFoundException cnfe) 691: { 692: return null; 693: } 694: catch (IllegalAccessException iae) 695: { 696: return null; 697: } 698: catch (InstantiationException ie) 699: { 700: return null; 701: } 702: } 703: 704: }