Source for java.beans.Introspector

   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 &lt;bean class name&gt;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 &lt;type&gt; getXXX(int)</CODE>
 110:  *     method, then XXX is a read-only indexed property of
 111:  *     type &lt;type&gt;.  If there is a
 112:  *     <CODE>public void setXXX(int,&lt;type&gt;)</CODE>
 113:  *     method, then it is a read-write indexed property of
 114:  *     type &lt;type&gt;.  There may also be a
 115:  *     <CODE>public &lt;type&gt;[] getXXX()</CODE> and a
 116:  *     <CODE>public void setXXX(&lt;type&gt;)</CODE>
 117:  *     method as well.</LI>
 118:  * <LI>If there is a
 119:  *     <CODE>public void setXXX(int,&lt;type&gt;)</CODE>
 120:  *     method, then it is a write-only indexed property of
 121:  *     type &lt;type&gt;.  There may also be a
 122:  *     <CODE>public &lt;type&gt;[] getXXX()</CODE> and a
 123:  *     <CODE>public void setXXX(&lt;type&gt;)</CODE>
 124:  *     method as well.</LI>
 125:  * <LI>If there is a
 126:  *     <CODE>public &lt;type&gt; getXXX()</CODE> method,
 127:  *     then XXX is a read-only property of type
 128:  *     &lt;type&gt;.  If there is a
 129:  *     <CODE>public void setXXX(&lt;type&gt;)</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(&lt;type&gt;)</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 &lt;type&gt;.</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(&lt;type&gt;)</CODE> and
 151:  * <CODE>public void removeXXX(&lt;type&gt;)</CODE>, where
 152:  * &lt;type&gt; 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 &lt;type&gt;.<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: }