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 Introspector()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 */ getBeanInfo(Class<?> beanClass)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 */ getBeanInfo(Class<?> beanClass, int flag)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 */ flushCaches()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 */ flushFromCaches(Class<?> clz)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 */ merge(BeanInfoEmbryo infoEmbryo, ExplicitInfo explicit)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 */ getBeanInfo(Class<?> beanClass, Class<?> stopClass)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 */ getBeanInfoSearchPath()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 */ setBeanInfoSearchPath(String[] beanInfoSearchPath)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 */ decapitalize(String name)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 copyBeanInfo(BeanInfo b)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 ExplicitInfo(Class beanClass, Class stopClass)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 */ flushCaches()618 static void flushCaches() { 619 explicitBeanInfos.clear(); 620 emptyBeanInfos.clear(); 621 } 622 findExplicitBeanInfo(Class beanClass)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 reallyFindExplicitBeanInfo(Class beanClass)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 */ getBeanInfo(ClassLoader cl, String infoName)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 } 706