GNU Classpath (0.18) | ||
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: * Flush all of the Introspector's internal caches. 217: * 218: * @since 1.2 219: */ 220: public static void flushCaches() 221: { 222: beanInfoCache.clear(); 223: 224: // Clears all the intermediate ExplicitInfo instances which 225: // have been created. 226: // This makes sure we have to retrieve stuff like BeanDescriptors 227: // again. (Remember that FeatureDescriptor can be modified by the user.) 228: ExplicitInfo.flushCaches(); 229: } 230: 231: /** 232: * Flush the Introspector's internal cached information for a given 233: * class. 234: * 235: * @param clz the class to be flushed. 236: * @throws NullPointerException if clz is null. 237: * @since 1.2 238: */ 239: public static void flushFromCaches(Class clz) 240: { 241: synchronized (clz) 242: { 243: beanInfoCache.remove(clz); 244: } 245: } 246: 247: /** 248: * Get the BeanInfo for class <CODE>beanClass</CODE>, 249: * first by looking for explicit information, next by 250: * using standard design patterns to determine 251: * information about the class. It crawls up the 252: * inheritance tree until it hits <CODE>topClass</CODE>. 253: * 254: * @param beanClass the Bean class. 255: * @param stopClass the class to stop at. 256: * @return the BeanInfo object representing the class. 257: */ 258: public static BeanInfo getBeanInfo(Class beanClass, Class stopClass) 259: throws IntrospectionException 260: { 261: ExplicitInfo explicit = new ExplicitInfo(beanClass, stopClass); 262: 263: IntrospectionIncubator ii = new IntrospectionIncubator(); 264: ii.setPropertyStopClass(explicit.propertyStopClass); 265: ii.setEventStopClass(explicit.eventStopClass); 266: ii.setMethodStopClass(explicit.methodStopClass); 267: ii.addMethods(beanClass.getMethods()); 268: 269: BeanInfoEmbryo currentInfo = ii.getBeanInfoEmbryo(); 270: PropertyDescriptor[] p = explicit.explicitPropertyDescriptors; 271: if(p!=null) 272: { 273: for(int i=0;i<p.length;i++) 274: { 275: if(!currentInfo.hasProperty(p[i])) 276: { 277: currentInfo.addProperty(p[i]); 278: } 279: } 280: if(explicit.defaultProperty != -1) 281: { 282: currentInfo.setDefaultPropertyName(p[explicit.defaultProperty].getName()); 283: } 284: } 285: EventSetDescriptor[] e = explicit.explicitEventSetDescriptors; 286: if(e!=null) 287: { 288: for(int i=0;i<e.length;i++) 289: { 290: if(!currentInfo.hasEvent(e[i])) 291: { 292: currentInfo.addEvent(e[i]); 293: } 294: } 295: if(explicit.defaultEvent != -1) 296: { 297: currentInfo.setDefaultEventName(e[explicit.defaultEvent].getName()); 298: } 299: } 300: MethodDescriptor[] m = explicit.explicitMethodDescriptors; 301: if(m!=null) 302: { 303: for(int i=0;i<m.length;i++) 304: { 305: if(!currentInfo.hasMethod(m[i])) 306: { 307: currentInfo.addMethod(m[i]); 308: } 309: } 310: } 311: 312: // Sets the info's BeanDescriptor to the one we extracted from the 313: // explicit BeanInfo instance(s) if they contained one. Otherwise we 314: // create the BeanDescriptor from scratch. 315: // Note: We do not create a copy the retrieved BeanDescriptor which will allow 316: // the user to modify the instance while it is cached. However this is how 317: // the RI does it. 318: currentInfo.setBeanDescriptor( 319: (explicit.explicitBeanDescriptor == null ? 320: new BeanDescriptor(beanClass, null) : 321: explicit.explicitBeanDescriptor)); 322: 323: currentInfo.setAdditionalBeanInfo(explicit.explicitBeanInfo); 324: currentInfo.setIcons(explicit.im); 325: 326: return currentInfo.getBeanInfo(); 327: } 328: 329: /** 330: * Get the search path for BeanInfo classes. 331: * 332: * @return the BeanInfo search path. 333: */ 334: public static String[] getBeanInfoSearchPath() 335: { 336: return beanInfoSearchPath; 337: } 338: 339: /** 340: * Set the search path for BeanInfo classes. 341: * @param beanInfoSearchPath the new BeanInfo search 342: * path. 343: */ 344: public static void setBeanInfoSearchPath(String[] beanInfoSearchPath) 345: { 346: Introspector.beanInfoSearchPath = beanInfoSearchPath; 347: } 348: 349: /** 350: * A helper method to convert a name to standard Java 351: * naming conventions: anything with two capitals as the 352: * first two letters remains the same, otherwise the 353: * first letter is decapitalized. URL = URL, I = i, 354: * MyMethod = myMethod. 355: * 356: * @param name the name to decapitalize. 357: * @return the decapitalized name. 358: */ 359: public static String decapitalize(String name) 360: { 361: try 362: { 363: if(!Character.isUpperCase(name.charAt(0))) 364: { 365: return name; 366: } 367: else 368: { 369: try 370: { 371: if(Character.isUpperCase(name.charAt(1))) 372: { 373: return name; 374: } 375: else 376: { 377: char[] c = name.toCharArray(); 378: c[0] = Character.toLowerCase(c[0]); 379: return new String(c); 380: } 381: } 382: catch(StringIndexOutOfBoundsException E) 383: { 384: char[] c = new char[1]; 385: c[0] = Character.toLowerCase(name.charAt(0)); 386: return new String(c); 387: } 388: } 389: } 390: catch(StringIndexOutOfBoundsException E) 391: { 392: return name; 393: } 394: catch(NullPointerException E) 395: { 396: return null; 397: } 398: } 399: 400: static BeanInfo copyBeanInfo(BeanInfo b) 401: { 402: java.awt.Image[] icons = new java.awt.Image[4]; 403: for(int i=1;i<=4;i++) 404: { 405: icons[i-1] = b.getIcon(i); 406: } 407: 408: return new ExplicitBeanInfo(b.getBeanDescriptor(), 409: b.getAdditionalBeanInfo(), 410: b.getPropertyDescriptors(), 411: b.getDefaultPropertyIndex(), 412: b.getEventSetDescriptors(), 413: b.getDefaultEventIndex(), 414: b.getMethodDescriptors(), 415: icons); 416: } 417: } 418: 419: class ExplicitInfo 420: { 421: BeanDescriptor explicitBeanDescriptor; 422: BeanInfo[] explicitBeanInfo; 423: 424: PropertyDescriptor[] explicitPropertyDescriptors; 425: EventSetDescriptor[] explicitEventSetDescriptors; 426: MethodDescriptor[] explicitMethodDescriptors; 427: 428: int defaultProperty; 429: int defaultEvent; 430: 431: java.awt.Image[] im = new java.awt.Image[4]; 432: 433: Class propertyStopClass; 434: Class eventStopClass; 435: Class methodStopClass; 436: 437: static Hashtable explicitBeanInfos = new Hashtable(); 438: static Vector emptyBeanInfos = new Vector(); 439: 440: ExplicitInfo(Class beanClass, Class stopClass) 441: { 442: while(beanClass != null && !beanClass.equals(stopClass)) 443: { 444: 445: BeanInfo explicit = findExplicitBeanInfo(beanClass); 446: 447: 448: if(explicit != null) 449: { 450: 451: if(explicitBeanDescriptor == null) 452: { 453: explicitBeanDescriptor = explicit.getBeanDescriptor(); 454: } 455: 456: if(explicitBeanInfo == null) 457: { 458: explicitBeanInfo = explicit.getAdditionalBeanInfo(); 459: } 460: 461: if(explicitPropertyDescriptors == null) 462: { 463: if(explicit.getPropertyDescriptors() != null) 464: { 465: explicitPropertyDescriptors = explicit.getPropertyDescriptors(); 466: defaultProperty = explicit.getDefaultPropertyIndex(); 467: propertyStopClass = beanClass; 468: } 469: } 470: 471: if(explicitEventSetDescriptors == null) 472: { 473: if(explicit.getEventSetDescriptors() != null) 474: { 475: explicitEventSetDescriptors = explicit.getEventSetDescriptors(); 476: defaultEvent = explicit.getDefaultEventIndex(); 477: eventStopClass = beanClass; 478: } 479: } 480: 481: if(explicitMethodDescriptors == null) 482: { 483: if(explicit.getMethodDescriptors() != null) 484: { 485: explicitMethodDescriptors = explicit.getMethodDescriptors(); 486: methodStopClass = beanClass; 487: } 488: } 489: 490: if(im[0] == null && im[1] == null 491: && im[2] == null && im[3] == null) 492: { 493: im[0] = explicit.getIcon(0); 494: im[1] = explicit.getIcon(1); 495: im[2] = explicit.getIcon(2); 496: im[3] = explicit.getIcon(3); 497: } 498: } 499: beanClass = beanClass.getSuperclass(); 500: } 501: 502: if(propertyStopClass == null) 503: { 504: propertyStopClass = stopClass; 505: } 506: 507: if(eventStopClass == null) 508: { 509: eventStopClass = stopClass; 510: } 511: 512: if(methodStopClass == null) 513: { 514: methodStopClass = stopClass; 515: } 516: } 517: 518: /** Throws away all cached data and makes sure we re-instantiate things 519: * like BeanDescriptors again. 520: */ 521: static void flushCaches() { 522: explicitBeanInfos.clear(); 523: emptyBeanInfos.clear(); 524: } 525: 526: static BeanInfo findExplicitBeanInfo(Class beanClass) 527: { 528: BeanInfo retval = (BeanInfo)explicitBeanInfos.get(beanClass); 529: if(retval != null) 530: { 531: return retval; 532: } 533: else if(emptyBeanInfos.indexOf(beanClass) != -1) 534: { 535: return null; 536: } 537: else 538: { 539: retval = reallyFindExplicitBeanInfo(beanClass); 540: if(retval != null) 541: { 542: explicitBeanInfos.put(beanClass,retval); 543: } 544: else 545: { 546: emptyBeanInfos.addElement(beanClass); 547: } 548: return retval; 549: } 550: } 551: 552: static BeanInfo reallyFindExplicitBeanInfo(Class beanClass) 553: { 554: ClassLoader beanClassLoader = beanClass.getClassLoader(); 555: BeanInfo beanInfo; 556: 557: beanInfo = getBeanInfo(beanClassLoader, beanClass.getName() + "BeanInfo"); 558: if (beanInfo == null) 559: { 560: String newName; 561: newName = ClassHelper.getTruncatedClassName(beanClass) + "BeanInfo"; 562: 563: for(int i = 0; i < Introspector.beanInfoSearchPath.length; i++) 564: { 565: if (Introspector.beanInfoSearchPath[i].equals("")) 566: beanInfo = getBeanInfo(beanClassLoader, newName); 567: else 568: beanInfo = getBeanInfo(beanClassLoader, 569: Introspector.beanInfoSearchPath[i] + "." 570: + newName); 571: 572: // Returns the beanInfo if it exists and the described class matches 573: // the one we searched. 574: if (beanInfo != null && beanInfo.getBeanDescriptor() != null && 575: beanInfo.getBeanDescriptor().getBeanClass() == beanClass) 576: 577: return beanInfo; 578: } 579: } 580: 581: return beanInfo; 582: } 583: 584: /** 585: * Returns an instance of the given class name when it can be loaded 586: * through the given class loader, or null otherwise. 587: */ 588: private static BeanInfo getBeanInfo(ClassLoader cl, String infoName) 589: { 590: try 591: { 592: return (BeanInfo) Class.forName(infoName, true, cl).newInstance(); 593: } 594: catch (ClassNotFoundException cnfe) 595: { 596: return null; 597: } 598: catch (IllegalAccessException iae) 599: { 600: return null; 601: } 602: catch (InstantiationException ie) 603: { 604: return null; 605: } 606: } 607: 608: }
GNU Classpath (0.18) |