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<Class<?>,BeanInfo> beanInfoCache = 186: new Hashtable<Class<?>,BeanInfo>(); 187: 188: private Introspector() {} 189: 190: /** 191: * Get the BeanInfo for class <CODE>beanClass</CODE>, 192: * first by looking for explicit information, next by 193: * using standard design patterns to determine 194: * information about the class. 195: * 196: * @param beanClass the class to get BeanInfo about. 197: * @return the BeanInfo object representing the class. 198: */ 199: public static BeanInfo getBeanInfo(Class<?> beanClass) 200: throws IntrospectionException 201: { 202: BeanInfo cachedInfo; 203: synchronized(beanClass) 204: { 205: cachedInfo = beanInfoCache.get(beanClass); 206: if(cachedInfo != null) 207: { 208: return cachedInfo; 209: } 210: cachedInfo = getBeanInfo(beanClass,null); 211: beanInfoCache.put(beanClass,cachedInfo); 212: return cachedInfo; 213: } 214: } 215: 216: /** 217: * Returns a {@BeanInfo} instance for the given Bean class where a flag 218: * controls the usage of explicit BeanInfo class to retrieve that 219: * information. 220: * 221: * <p>You have three options:</p> 222: * <p>With {@link #USE_ALL_BEANINFO} the result is the same as 223: * {@link #getBeanInfo(Class)}.</p> 224: * 225: * <p>Calling the method with <code>flag</code> set to 226: * {@link #IGNORE_IMMEDIATE_BEANINFO} will let it use all 227: * explicit BeanInfo classes for the beans superclasses 228: * but not for the bean class itself. Furthermore eventset, 229: * property and method information is retrieved by introspection 230: * if the explicit <code>BeanInfos</code> did not provide such data 231: * (ie. return <code>null</code> on {@link BeanInfo.getMethodDescriptors}, 232: * {@link BeanInfo.getEventSetDescriptors} and 233: * {@link BeanInfo.getPropertyDescriptors}.) 234: * </p> 235: * 236: * <p>When the method is called with <code>flag</code< set to 237: * {@link #IGNORE_ALL_BEANINFO} all the bean data is retrieved 238: * by inspecting the class.</p> 239: * 240: * <p>Note: Any unknown value for <code>flag</code> is interpreted 241: * as {@link #IGNORE_ALL_BEANINFO}</p>. 242: * 243: * @param beanClass The class whose BeanInfo should be returned. 244: * @param flag Controls the usage of explicit <code>BeanInfo</code> classes. 245: * @return A BeanInfo object describing the class. 246: * @throws IntrospectionException If something goes wrong while retrieving 247: * the bean data. 248: */ 249: public static BeanInfo getBeanInfo(Class<?> beanClass, int flag) 250: throws IntrospectionException 251: { 252: IntrospectionIncubator ii; 253: BeanInfoEmbryo infoEmbryo; 254: 255: switch(flag) 256: { 257: case USE_ALL_BEANINFO: 258: return getBeanInfo(beanClass); 259: case IGNORE_IMMEDIATE_BEANINFO: 260: Class superclass = beanClass.getSuperclass(); 261: ExplicitInfo explicit = new ExplicitInfo(superclass, null); 262: 263: ii = new IntrospectionIncubator(); 264: if (explicit.explicitEventSetDescriptors != null) 265: ii.setEventStopClass(superclass); 266: 267: if (explicit.explicitMethodDescriptors != null) 268: ii.setMethodStopClass(superclass); 269: 270: if (explicit.explicitPropertyDescriptors != null) 271: ii.setPropertyStopClass(superclass); 272: 273: ii.addMethods(beanClass.getMethods()); 274: 275: infoEmbryo = ii.getBeanInfoEmbryo(); 276: merge(infoEmbryo, explicit); 277: 278: infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null)); 279: 280: return infoEmbryo.getBeanInfo(); 281: case IGNORE_ALL_BEANINFO: 282: default: 283: ii = new IntrospectionIncubator(); 284: ii.addMethods(beanClass.getMethods()); 285: infoEmbryo = ii.getBeanInfoEmbryo(); 286: infoEmbryo.setBeanDescriptor(new BeanDescriptor(beanClass, null)); 287: 288: return infoEmbryo.getBeanInfo(); 289: } 290: } 291: 292: /** 293: * Flush all of the Introspector's internal caches. 294: * 295: * @since 1.2 296: */ 297: public static void flushCaches() 298: { 299: beanInfoCache.clear(); 300: 301: // Clears all the intermediate ExplicitInfo instances which 302: // have been created. 303: // This makes sure we have to retrieve stuff like BeanDescriptors 304: // again. (Remember that FeatureDescriptor can be modified by the user.) 305: ExplicitInfo.flushCaches(); 306: } 307: 308: /** 309: * Flush the Introspector's internal cached information for a given 310: * class. 311: * 312: * @param clz the class to be flushed. 313: * @throws NullPointerException if clz is null. 314: * @since 1.2 315: */ 316: public static void flushFromCaches(Class<?> clz) 317: { 318: synchronized (clz) 319: { 320: beanInfoCache.remove(clz); 321: } 322: } 323: 324: /** Adds all explicity given bean info data to the introspected 325: * data. 326: * 327: * @param infoEmbryo Bean info data retrieved by introspection. 328: * @param explicit Bean info data retrieved by BeanInfo classes. 329: */ 330: private static void merge(BeanInfoEmbryo infoEmbryo, ExplicitInfo explicit) 331: { 332: PropertyDescriptor[] p = explicit.explicitPropertyDescriptors; 333: if(p!=null) 334: { 335: for(int i=0;i<p.length;i++) 336: { 337: if(!infoEmbryo.hasProperty(p[i])) 338: { 339: infoEmbryo.addProperty(p[i]); 340: } 341: } 342: 343: // -1 should be used to denote a missing default property but 344: // for robustness reasons any value below zero is discarded. 345: // Not doing so would let Classpath fail where the JDK succeeds. 346: if(explicit.defaultProperty > -1) 347: { 348: infoEmbryo.setDefaultPropertyName(p[explicit.defaultProperty].getName()); 349: } 350: } 351: EventSetDescriptor[] e = explicit.explicitEventSetDescriptors; 352: if(e!=null) 353: { 354: for(int i=0;i<e.length;i++) 355: { 356: if(!infoEmbryo.hasEvent(e[i])) 357: { 358: infoEmbryo.addEvent(e[i]); 359: } 360: } 361: 362: // -1 should be used to denote a missing default event but 363: // for robustness reasons any value below zero is discarded. 364: // Not doing so would let Classpath fail where the JDK succeeds. 365: if(explicit.defaultEvent > -1) 366: { 367: infoEmbryo.setDefaultEventName(e[explicit.defaultEvent].getName()); 368: } 369: } 370: MethodDescriptor[] m = explicit.explicitMethodDescriptors; 371: if(m!=null) 372: { 373: for(int i=0;i<m.length;i++) 374: { 375: if(!infoEmbryo.hasMethod(m[i])) 376: { 377: infoEmbryo.addMethod(m[i]); 378: } 379: } 380: } 381: 382: infoEmbryo.setAdditionalBeanInfo(explicit.explicitBeanInfo); 383: infoEmbryo.setIcons(explicit.im); 384: 385: } 386: 387: /** 388: * Get the BeanInfo for class <CODE>beanClass</CODE>, 389: * first by looking for explicit information, next by 390: * using standard design patterns to determine 391: * information about the class. It crawls up the 392: * inheritance tree until it hits <CODE>topClass</CODE>. 393: * 394: * @param beanClass the Bean class. 395: * @param stopClass the class to stop at. 396: * @return the BeanInfo object representing the class. 397: */ 398: public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass) 399: throws IntrospectionException 400: { 401: ExplicitInfo explicit = new ExplicitInfo(beanClass, stopClass); 402: 403: IntrospectionIncubator ii = new IntrospectionIncubator(); 404: ii.setPropertyStopClass(explicit.propertyStopClass); 405: ii.setEventStopClass(explicit.eventStopClass); 406: ii.setMethodStopClass(explicit.methodStopClass); 407: ii.addMethods(beanClass.getMethods()); 408: 409: BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo(); 410: 411: merge(currentInfo, explicit); 412: 413: // Sets the info's BeanDescriptor to the one we extracted from the 414: // explicit BeanInfo instance(s) if they contained one. Otherwise we 415: // create the BeanDescriptor from scratch. 416: // Note: We do not create a copy the retrieved BeanDescriptor which will allow 417: // the user to modify the instance while it is cached. However this is how 418: // the RI does it. 419: currentInfo.setBeanDescriptor( 420: (explicit.explicitBeanDescriptor == null ? 421: new BeanDescriptor(beanClass, null) : 422: explicit.explicitBeanDescriptor)); 423: return currentInfo.getBeanInfo(); 424: } 425: 426: /** 427: * Get the search path for BeanInfo classes. 428: * 429: * @return the BeanInfo search path. 430: */ 431: public static String[] getBeanInfoSearchPath() 432: { 433: return beanInfoSearchPath; 434: } 435: 436: /** 437: * Set the search path for BeanInfo classes. 438: * @param beanInfoSearchPath the new BeanInfo search 439: * path. 440: */ 441: public static void setBeanInfoSearchPath(String[] beanInfoSearchPath) 442: { 443: Introspector.beanInfoSearchPath = beanInfoSearchPath; 444: } 445: 446: /** 447: * A helper method to convert a name to standard Java 448: * naming conventions: anything with two capitals as the 449: * first two letters remains the same, otherwise the 450: * first letter is decapitalized. URL = URL, I = i, 451: * MyMethod = myMethod. 452: * 453: * @param name the name to decapitalize. 454: * @return the decapitalized name. 455: */ 456: public static String decapitalize(String name) 457: { 458: try 459: { 460: if(!Character.isUpperCase(name.charAt(0))) 461: { 462: return name; 463: } 464: else 465: { 466: try 467: { 468: if(Character.isUpperCase(name.charAt(1))) 469: { 470: return name; 471: } 472: else 473: { 474: char[] c = name.toCharArray(); 475: c[0] = Character.toLowerCase(c[0]); 476: return new String(c); 477: } 478: } 479: catch(StringIndexOutOfBoundsException E) 480: { 481: char[] c = new char[1]; 482: c[0] = Character.toLowerCase(name.charAt(0)); 483: return new String(c); 484: } 485: } 486: } 487: catch(StringIndexOutOfBoundsException E) 488: { 489: return name; 490: } 491: catch(NullPointerException E) 492: { 493: return null; 494: } 495: } 496: 497: static BeanInfo copyBeanInfo(BeanInfo b) 498: { 499: java.awt.Image[] icons = new java.awt.Image[4]; 500: for(int i=1;i<=4;i++) 501: { 502: icons[i-1] = b.getIcon(i); 503: } 504: 505: return new ExplicitBeanInfo(b.getBeanDescriptor(), 506: b.getAdditionalBeanInfo(), 507: b.getPropertyDescriptors(), 508: b.getDefaultPropertyIndex(), 509: b.getEventSetDescriptors(), 510: b.getDefaultEventIndex(), 511: b.getMethodDescriptors(), 512: icons); 513: } 514: } 515: 516: class ExplicitInfo 517: { 518: BeanDescriptor explicitBeanDescriptor; 519: BeanInfo[] explicitBeanInfo; 520: 521: PropertyDescriptor[] explicitPropertyDescriptors; 522: EventSetDescriptor[] explicitEventSetDescriptors; 523: MethodDescriptor[] explicitMethodDescriptors; 524: 525: int defaultProperty; 526: int defaultEvent; 527: 528: java.awt.Image[] im = new java.awt.Image[4]; 529: 530: Class propertyStopClass; 531: Class eventStopClass; 532: Class methodStopClass; 533: 534: static Hashtable explicitBeanInfos = new Hashtable(); 535: static Vector emptyBeanInfos = new Vector(); 536: 537: ExplicitInfo(Class beanClass, Class stopClass) 538: { 539: while(beanClass != null && !beanClass.equals(stopClass)) 540: { 541: 542: BeanInfo explicit = findExplicitBeanInfo(beanClass); 543: 544: 545: if(explicit != null) 546: { 547: 548: if(explicitBeanDescriptor == null) 549: { 550: explicitBeanDescriptor = explicit.getBeanDescriptor(); 551: } 552: 553: if(explicitBeanInfo == null) 554: { 555: explicitBeanInfo = explicit.getAdditionalBeanInfo(); 556: } 557: 558: if(explicitPropertyDescriptors == null) 559: { 560: if(explicit.getPropertyDescriptors() != null) 561: { 562: explicitPropertyDescriptors = explicit.getPropertyDescriptors(); 563: defaultProperty = explicit.getDefaultPropertyIndex(); 564: propertyStopClass = beanClass; 565: } 566: } 567: 568: if(explicitEventSetDescriptors == null) 569: { 570: if(explicit.getEventSetDescriptors() != null) 571: { 572: explicitEventSetDescriptors = explicit.getEventSetDescriptors(); 573: defaultEvent = explicit.getDefaultEventIndex(); 574: eventStopClass = beanClass; 575: } 576: } 577: 578: if(explicitMethodDescriptors == null) 579: { 580: if(explicit.getMethodDescriptors() != null) 581: { 582: explicitMethodDescriptors = explicit.getMethodDescriptors(); 583: methodStopClass = beanClass; 584: } 585: } 586: 587: if(im[0] == null && im[1] == null 588: && im[2] == null && im[3] == null) 589: { 590: im[0] = explicit.getIcon(0); 591: im[1] = explicit.getIcon(1); 592: im[2] = explicit.getIcon(2); 593: im[3] = explicit.getIcon(3); 594: } 595: } 596: beanClass = beanClass.getSuperclass(); 597: } 598: 599: if(propertyStopClass == null) 600: { 601: propertyStopClass = stopClass; 602: } 603: 604: if(eventStopClass == null) 605: { 606: eventStopClass = stopClass; 607: } 608: 609: if(methodStopClass == null) 610: { 611: methodStopClass = stopClass; 612: } 613: } 614: 615: /** Throws away all cached data and makes sure we re-instantiate things 616: * like BeanDescriptors again. 617: */ 618: static void flushCaches() { 619: explicitBeanInfos.clear(); 620: emptyBeanInfos.clear(); 621: } 622: 623: static BeanInfo findExplicitBeanInfo(Class beanClass) 624: { 625: BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass); 626: if(retval != null) 627: { 628: return retval; 629: } 630: else if(emptyBeanInfos.indexOf(beanClass) != -1) 631: { 632: return null; 633: } 634: else 635: { 636: retval = reallyFindExplicitBeanInfo(beanClass); 637: if(retval != null) 638: { 639: explicitBeanInfos.put(beanClass,retval); 640: } 641: else 642: { 643: emptyBeanInfos.addElement(beanClass); 644: } 645: return retval; 646: } 647: } 648: 649: static BeanInfo reallyFindExplicitBeanInfo(Class beanClass) 650: { 651: ClassLoader beanClassLoader = beanClass.getClassLoader(); 652: BeanInfo beanInfo; 653: 654: beanInfo = getBeanInfo(beanClassLoader, beanClass.getName() + "BeanInfo"); 655: if (beanInfo == null) 656: { 657: String newName; 658: newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo"; 659: 660: for(int i = 0; i < Introspector.beanInfoSearchPath.length; i++) 661: { 662: if (Introspector.beanInfoSearchPath[i].equals("")) 663: beanInfo = getBeanInfo(beanClassLoader, newName); 664: else 665: beanInfo = getBeanInfo(beanClassLoader, 666: Introspector.beanInfoSearchPath[i] + "." 667: + newName); 668: 669: // Returns the beanInfo if it exists and the described class matches 670: // the one we searched. 671: if (beanInfo != null && beanInfo.getBeanDescriptor() != null && 672: beanInfo.getBeanDescriptor().getBeanClass() == beanClass) 673: 674: return beanInfo; 675: } 676: } 677: 678: return beanInfo; 679: } 680: 681: /** 682: * Returns an instance of the given class name when it can be loaded 683: * through the given class loader, or null otherwise. 684: */ 685: private static BeanInfo getBeanInfo(ClassLoader cl, String infoName) 686: { 687: try 688: { 689: return (BeanInfo) Class.forName(infoName, true, cl).newInstance(); 690: } 691: catch (ClassNotFoundException cnfe) 692: { 693: return null; 694: } 695: catch (IllegalAccessException iae) 696: { 697: return null; 698: } 699: catch (InstantiationException ie) 700: { 701: return null; 702: } 703: } 704: 705: }