Source for java.beans.PropertyDescriptor

   1: /* java.beans.PropertyDescriptor
   2:    Copyright (C) 1998, 2001, 2004, 2005  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: package java.beans;
  39: 
  40: import java.lang.reflect.Method;
  41: 
  42: /**
  43:  ** PropertyDescriptor describes information about a JavaBean property,
  44:  ** by which we mean a property that has been exposed via a pair of
  45:  ** get and set methods.  (There may be no get method, which means
  46:  ** the property is write-only, or no set method, which means the
  47:  ** the property is read-only.)<P>
  48:  **
  49:  ** The constraints put on get and set methods are:<P>
  50:  ** <OL>
  51:  ** <LI>A get method must have signature
  52:  **     <CODE>&lt;propertyType&gt; &lt;getMethodName&gt;()</CODE></LI>
  53:  ** <LI>A set method must have signature
  54:  **     <CODE>void &lt;setMethodName&gt;(&lt;propertyType&gt;)</CODE></LI>
  55:  ** <LI>Either method type may throw any exception.</LI>
  56:  ** <LI>Both methods must be public.</LI>
  57:  ** </OL>
  58:  **
  59:  ** @author John Keiser
  60:  ** @author Robert Schuster (thebohemian@gmx.net)
  61:  ** @since 1.1
  62:  ** @status updated to 1.4
  63:  **/
  64: public class PropertyDescriptor extends FeatureDescriptor
  65: {
  66:     Class propertyType;
  67:     Method getMethod;
  68:     Method setMethod;
  69: 
  70:     Class propertyEditorClass;
  71:     boolean bound;
  72:     boolean constrained;
  73: 
  74:     PropertyDescriptor(String name)
  75:     {
  76:         setName(name);
  77:     }
  78: 
  79:     /** Create a new PropertyDescriptor by introspection.
  80:      ** This form of constructor creates the PropertyDescriptor by
  81:      ** looking for a getter method named <CODE>get&lt;name&gt;()</CODE>
  82:      ** (or, optionally, if the property is boolean,
  83:      ** <CODE>is&lt;name&gt;()</CODE>) and
  84:      ** <CODE>set&lt;name&gt;()</CODE> in class
  85:      ** <CODE>&lt;beanClass&gt;</CODE>, where &lt;name&gt; has its
  86:      ** first letter capitalized by the constructor.<P>
  87:      **
  88:      ** Note that using this constructor the given property must be read- <strong>and</strong>
  89:      ** writeable. If the implementation does not both, a read and a write method, an
  90:      ** <code>IntrospectionException</code> is thrown.
  91:      **
  92:      ** <B>Implementation note:</B> If there is both are both isXXX and
  93:      ** getXXX methods, the former is used in preference to the latter.
  94:      ** We do not check that an isXXX method returns a boolean. In both
  95:      ** cases, this matches the behaviour of JDK 1.4<P>
  96:      **
  97:      ** @param name the programmatic name of the property, usually
  98:      **             starting with a lowercase letter (e.g. fooManChu
  99:      **             instead of FooManChu).
 100:      ** @param beanClass the class the get and set methods live in.
 101:      ** @exception IntrospectionException if the methods are not found 
 102:      **            or invalid.
 103:      **/
 104:     public PropertyDescriptor(String name, Class beanClass)
 105:         throws IntrospectionException
 106:     {
 107:         setName(name);
 108:         if (name.length() == 0)
 109:         {
 110:             throw new IntrospectionException("empty property name");
 111:         }
 112:         String caps = Character.toUpperCase(name.charAt(0)) + name.substring(1);
 113:         findMethods(beanClass, "is" + caps, "get" + caps, "set" + caps);
 114: 
 115:         if (getMethod == null)
 116:         {
 117:             throw new IntrospectionException(
 118:                 "Cannot find a is" + caps + " or get" + caps + " method");
 119:         }
 120: 
 121:         if (setMethod == null)
 122:         {
 123:             throw new IntrospectionException(
 124:                 "Cannot find a " + caps + " method");
 125:         }
 126: 
 127:         // finally check the methods compatibility        
 128:         propertyType = checkMethods(getMethod, setMethod);
 129:     }
 130: 
 131:     /** Create a new PropertyDescriptor by introspection.
 132:      ** This form of constructor allows you to specify the
 133:      ** names of the get and set methods to search for.<P>
 134:      **
 135:      ** <B>Implementation note:</B> If there is a get method (or
 136:      ** boolean isXXX() method), then the return type of that method
 137:      ** is used to find the set method.  If there is no get method,
 138:      ** then the set method is searched for exhaustively.<P>
 139:      **
 140:      ** <B>Spec note:</B>
 141:      ** If there is no get method and multiple set methods with
 142:      ** the same name and a single parameter (different type of course),
 143:      ** then an IntrospectionException is thrown.  While Sun's spec
 144:      ** does not state this, it can make Bean behavior different on
 145:      ** different systems (since method order is not guaranteed) and as
 146:      ** such, can be treated as a bug in the spec.  I am not aware of
 147:      ** whether Sun's implementation catches this.
 148:      **
 149:      ** @param name the programmatic name of the property, usually
 150:      **             starting with a lowercase letter (e.g. fooManChu
 151:      **             instead of FooManChu).
 152:      ** @param beanClass the class the get and set methods live in.
 153:      ** @param getMethodName the name of the get method or <code>null</code> if the property is write-only.
 154:      ** @param setMethodName the name of the set method or <code>null</code> if the property is read-only.
 155:      ** @exception IntrospectionException if the methods are not found 
 156:      **            or invalid.
 157:      **/
 158:     public PropertyDescriptor(
 159:         String name,
 160:         Class beanClass,
 161:         String getMethodName,
 162:         String setMethodName)
 163:         throws IntrospectionException
 164:     {
 165:         setName(name);
 166:         findMethods(beanClass, getMethodName, null, setMethodName);
 167: 
 168:         if (getMethod == null && getMethodName != null)
 169:         {
 170:             throw new IntrospectionException(
 171:                 "Cannot find a getter method called " + getMethodName);
 172:         }
 173: 
 174:         if (setMethod == null && setMethodName != null)
 175:         {
 176:             throw new IntrospectionException(
 177:                 "Cannot find a setter method called " + setMethodName);
 178:         }
 179: 
 180:         propertyType = checkMethods(getMethod, setMethod);
 181:     }
 182: 
 183:     /** Create a new PropertyDescriptor using explicit Methods.
 184:      ** Note that the methods will be checked for conformance to standard
 185:      ** Property method rules, as described above at the top of this class.
 186:      **<br>
 187:      ** It is possible to call this method with both <code>Method</code> arguments
 188:      ** being <code>null</code>. In such a case the property type is <code>null</code>.
 189:      ** 
 190:      ** @param name the programmatic name of the property, usually
 191:      **             starting with a lowercase letter (e.g. fooManChu
 192:      **             instead of FooManChu).
 193:      ** @param readMethod the read method or <code>null</code> if the property is write-only.
 194:      ** @param writeMethod the write method or <code>null</code> if the property is read-only.
 195:      ** @exception IntrospectionException if the methods are not found 
 196:      **            or invalid.
 197:      **/
 198:     public PropertyDescriptor(
 199:         String name,
 200:         Method readMethod,
 201:         Method writeMethod)
 202:         throws IntrospectionException
 203:     {
 204:         setName(name);
 205:         getMethod = readMethod;
 206:         setMethod = writeMethod;
 207:         propertyType = checkMethods(getMethod, setMethod);
 208:     }
 209: 
 210:     /** Get the property type.
 211:      ** This is the type the get method returns and the set method
 212:      ** takes in.
 213:      **/
 214:     public Class getPropertyType()
 215:     {
 216:         return propertyType;
 217:     }
 218: 
 219:     /** Get the get method.  Why they call it readMethod here and
 220:      ** get everywhere else is beyond me.
 221:      **/
 222:     public Method getReadMethod()
 223:     {
 224:         return getMethod;
 225:     }
 226: 
 227:     /** Sets the read method.<br/>
 228:      * The read method is used to retrieve the value of a property. A legal
 229:      * read method must have no arguments. Its return type must not be
 230:      * <code>void</code>. If this methods succeeds the property type
 231:      * is adjusted to the return type of the read method.<br/>
 232:      * <br/>
 233:      * It is legal to set the read and the write method to <code>null</code>
 234:      * or provide method which have been declared in distinct classes.
 235:      * 
 236:      * @param readMethod The new method to be used or <code>null</code>.
 237:      * @throws IntrospectionException If the given method is invalid.
 238:      * @since 1.2
 239:      */
 240:     public void setReadMethod(Method readMethod) throws IntrospectionException
 241:     {
 242:         propertyType = checkMethods(readMethod, setMethod);
 243: 
 244:         getMethod = readMethod;
 245:     }
 246: 
 247:     /** Get the set method.  Why they call it writeMethod here and
 248:      ** set everywhere else is beyond me.
 249:      **/
 250:     public Method getWriteMethod()
 251:     {
 252:         return setMethod;
 253:     }
 254: 
 255:     /** Sets the write method.<br/>
 256:      * The write method is used to set the value of a property. A legal write method
 257:      * must have a single argument which can be assigned to the property. If no
 258:      * read method exists the property type changes to the argument type of the
 259:      * write method.<br/>
 260:      * <br/>
 261:      * It is legal to set the read and the write method to <code>null</code>
 262:      * or provide method which have been declared in distinct classes.
 263:      * 
 264:      * @param writeMethod The new method to be used or <code>null</code>.
 265:      * @throws IntrospectionException If the given method is invalid.
 266:      * @since 1.2
 267:      */
 268:     public void setWriteMethod(Method writeMethod)
 269:         throws IntrospectionException
 270:     {
 271:         propertyType = checkMethods(getMethod, writeMethod);
 272: 
 273:         setMethod = writeMethod;
 274:     }
 275: 
 276:     /** Get whether the property is bound.  Defaults to false. **/
 277:     public boolean isBound()
 278:     {
 279:         return bound;
 280:     }
 281: 
 282:     /** Set whether the property is bound.
 283:      ** As long as the the bean implements addPropertyChangeListener() and
 284:      ** removePropertyChangeListener(), setBound(true) may safely be called.<P>
 285:      ** If these things are not true, then the behavior of the system
 286:      ** will be undefined.<P>
 287:      **
 288:      ** When a property is bound, its set method is required to fire the
 289:      ** <CODE>PropertyChangeListener.propertyChange())</CODE> event
 290:      ** after the value has changed.
 291:      ** @param bound whether the property is bound or not.
 292:      **/
 293:     public void setBound(boolean bound)
 294:     {
 295:         this.bound = bound;
 296:     }
 297: 
 298:     /** Get whether the property is constrained.  Defaults to false. **/
 299:     public boolean isConstrained()
 300:     {
 301:         return constrained;
 302:     }
 303: 
 304:     /** Set whether the property is constrained.
 305:      ** If the set method throws <CODE>java.beans.PropertyVetoException</CODE>
 306:      ** (or subclass thereof) and the bean implements addVetoableChangeListener()
 307:      ** and removeVetoableChangeListener(), then setConstrained(true) may safely
 308:      ** be called.  Otherwise, the system behavior is undefined.
 309:      ** <B>Spec note:</B> given those strict parameters, it would be nice if it
 310:      ** got set automatically by detection, but oh well.<P>
 311:      ** When a property is constrained, its set method is required to:<P>
 312:      ** <OL>
 313:      ** <LI>Fire the <CODE>VetoableChangeListener.vetoableChange()</CODE>
 314:      **     event notifying others of the change and allowing them a chance to
 315:      **     say it is a bad thing.</LI>
 316:      ** <LI>If any of the listeners throws a PropertyVetoException, then
 317:      **     it must fire another vetoableChange() event notifying the others
 318:      **     of a reversion to the old value (though, of course, the change
 319:      **     was never made).  Then it rethrows the PropertyVetoException and
 320:      **     exits.</LI>
 321:      ** <LI>If all has gone well to this point, the value may be changed.</LI>
 322:      ** </OL>
 323:      ** @param constrained whether the property is constrained or not.
 324:      **/
 325:     public void setConstrained(boolean constrained)
 326:     {
 327:         this.constrained = constrained;
 328:     }
 329: 
 330:     /** Get the PropertyEditor class.  Defaults to null. **/
 331:     public Class getPropertyEditorClass()
 332:     {
 333:         return propertyEditorClass;
 334:     }
 335: 
 336:     /** Set the PropertyEditor class.  If the class does not implement
 337:      ** the PropertyEditor interface, you will likely get an exception
 338:      ** late in the game.
 339:      ** @param propertyEditorClass the PropertyEditor class for this 
 340:      **        class to use.
 341:      **/
 342:     public void setPropertyEditorClass(Class propertyEditorClass)
 343:     {
 344:         this.propertyEditorClass = propertyEditorClass;
 345:     }
 346: 
 347:     private void findMethods(
 348:         Class beanClass,
 349:         String getMethodName1,
 350:         String getMethodName2,
 351:         String setMethodName)
 352:         throws IntrospectionException
 353:     {
 354:         try
 355:         {
 356:             // Try the first get method name
 357:             if (getMethodName1 != null)
 358:             {
 359:                 try
 360:                 {
 361:                     getMethod =
 362:                         beanClass.getMethod(getMethodName1, new Class[0]);
 363:                 }
 364:                 catch (NoSuchMethodException e)
 365:                 {}
 366:             }
 367: 
 368:             // Fall back to the second get method name
 369:             if (getMethod == null && getMethodName2 != null)
 370:             {
 371:                 try
 372:                 {
 373:                     getMethod =
 374:                         beanClass.getMethod(getMethodName2, new Class[0]);
 375:                 }
 376:                 catch (NoSuchMethodException e)
 377:                 {}
 378:             }
 379: 
 380:             // Try the set method name
 381:             if (setMethodName != null)
 382:             {
 383:                 if (getMethod != null)
 384:                 {
 385:                     // If there is a get method, use its return type to help
 386:                     // select the corresponding set method.
 387:                     Class propertyType = getMethod.getReturnType();
 388:                     if (propertyType == Void.TYPE)
 389:                     {
 390:                         String msg =
 391:                             "The property's read method has return type 'void'";
 392:                         throw new IntrospectionException(msg);
 393:                     }
 394: 
 395:                     Class[] setArgs = new Class[] { propertyType };
 396:                     try
 397:                     {
 398:                         setMethod = beanClass.getMethod(setMethodName, setArgs);
 399:                     }
 400:                     catch (NoSuchMethodException e)
 401:                     {}
 402:                 }
 403:                 else if (getMethodName1 == null && getMethodName2 == null)
 404:                 {
 405:                     // If this is a write-only property, choose the first set method
 406:                     // with the required name, one parameter and return type 'void'
 407:                     Method[] methods = beanClass.getMethods();
 408:                     for (int i = 0; i < methods.length; i++)
 409:                     {
 410:                         if (methods[i].getName().equals(setMethodName)
 411:                             && methods[i].getParameterTypes().length == 1
 412:                             && methods[i].getReturnType() == Void.TYPE)
 413:                         {
 414:                             setMethod = methods[i];
 415:                             break;
 416:                         }
 417:                     }
 418:                 }
 419:             }
 420:         }
 421:         catch (SecurityException e)
 422:         {
 423:             // FIXME -- shouldn't we just allow SecurityException to propagate?
 424:             String msg =
 425:                 "SecurityException thrown on attempt to access methods.";
 426:             throw new IntrospectionException(msg);
 427:         }
 428:     }
 429: 
 430:     /** Checks whether the given <code>Method</code> instances are legal read and
 431:      * write methods. The following requirements must be met:<br/>
 432:      * <ul>
 433:      * <li>the read method must not have an argument</li>
 434:      * <li>the read method must have a non void return type</li>
 435:      * <li>the read method may not exist</li>
 436:      * <li>the write method must have a single argument</li>
 437:      * <li>the property type and the read method's return type must be assignable from the
 438:      * write method's argument type</li>
 439:      * <li>the write method may not exist</li>
 440:      * </ul>
 441:      * While checking the methods a common new property type is calculated. If the method
 442:      * succeeds this property type is returned.<br/>
 443:      * <br/>
 444:      * For compatibility this has to be noted:<br/>
 445:      * The two methods are allowed to be defined in two distinct classes and may both be null.
 446:      * 
 447:      * @param readMethod The new read method to check.
 448:      * @param writeMethod The new write method to check.
 449:      * @return The common property type of the two method.
 450:      * @throws IntrospectionException If any of the above requirements are not met.
 451:      */
 452:     private Class checkMethods(Method readMethod, Method writeMethod)
 453:         throws IntrospectionException
 454:     {
 455:         Class newPropertyType = propertyType;
 456: 
 457:         // a valid read method has zero arguments and a non-void return type.
 458:         if (readMethod != null)
 459:         {
 460:             if (readMethod.getParameterTypes().length > 0)
 461:             {
 462:                 throw new IntrospectionException("read method has unexpected parameters");
 463:             }
 464: 
 465:             newPropertyType = readMethod.getReturnType();
 466: 
 467:             if (newPropertyType == Void.TYPE)
 468:             {
 469:                 throw new IntrospectionException("read method return type is void");
 470:             }
 471:         }
 472: 
 473:         // a valid write method has one argument which can be assigned to the property
 474:         if (writeMethod != null)
 475:         {
 476:             if (writeMethod.getParameterTypes().length != 1)
 477:             {
 478:                 String msg = "write method does not have exactly one parameter";
 479:                 throw new IntrospectionException(msg);
 480:             }
 481: 
 482:             if (readMethod == null)
 483:             {
 484:                 // changes the property type if there is no read method
 485:                 newPropertyType = writeMethod.getParameterTypes()[0];
 486:             }
 487:             else
 488:             {
 489:                 // checks whether the write method can be assigned to the return type of the read
 490:                 // method (if this is not the case, the methods are not compatible)
 491:                 // note: newPropertyType may be null if no methods or method names have been
 492:                 // delivered in the constructor.
 493:                 if (newPropertyType != null
 494:                     && !newPropertyType.isAssignableFrom(
 495:                         writeMethod.getParameterTypes()[0]))
 496:                 {
 497:                     // note: newPropertyType is the same as readMethod.getReturnType() at this point
 498:                     throw new IntrospectionException("read and write method are not compatible");
 499:                 }
 500: 
 501:                 /* note: the check whether both method are defined in related classes makes sense but is not
 502:                  * done in the JDK. 
 503:                  * I leave this code here in case someone at Sun decides to add that functionality in later versions (rschuster)
 504:                 if ((!readMethod
 505:                     .getDeclaringClass()
 506:                     .isAssignableFrom(writeMethod.getDeclaringClass()))
 507:                     && (!writeMethod
 508:                         .getDeclaringClass()
 509:                         .isAssignableFrom(readMethod.getDeclaringClass())))
 510:                 {
 511:                     String msg =
 512:                         "set and get methods are not in the same class.";
 513:                     throw new IntrospectionException(msg);
 514:                 }
 515:                 */
 516: 
 517:             }
 518:         }
 519: 
 520:         return newPropertyType;
 521:     }
 522: 
 523:     /**
 524:      * Return a hash code for this object, conforming to the contract described
 525:      * in {@link Object#hashCode()}.
 526:      * @return the hash code
 527:      * @since 1.5
 528:      */
 529:     public int hashCode()
 530:     {
 531:       return ((propertyType == null ? 0 : propertyType.hashCode())
 532:               | (propertyEditorClass == null ? 0 : propertyEditorClass.hashCode())
 533:               | (bound ? Boolean.TRUE : Boolean.FALSE).hashCode()
 534:               | (constrained ? Boolean.TRUE : Boolean.FALSE).hashCode()
 535:               | (getMethod == null ? 0 : getMethod.hashCode())
 536:               | (setMethod == null ? 0 : setMethod.hashCode()));
 537:     }
 538: 
 539:     /** Compares this <code>PropertyDescriptor</code> against the
 540:      * given object.
 541:      * Two PropertyDescriptors are equals if
 542:      * <ul>
 543:      * <li>the read methods are equal</li>
 544:      * <li>the write methods are equal</li>
 545:      * <li>the property types are equals</li>
 546:      * <li>the property editor classes are equal</li>
 547:      * <li>the flags (constrained and bound) are equal</li>
 548:      * </ul>
 549:      * @return Whether both objects are equal according to the rules given above.
 550:      * @since 1.4
 551:     */
 552:     public boolean equals(Object o)
 553:     {
 554:         if (o instanceof PropertyDescriptor)
 555:         {
 556:             PropertyDescriptor that = (PropertyDescriptor) o;
 557: 
 558:             // compares the property types and checks the case where both are null
 559:             boolean samePropertyType =
 560:                 (propertyType == null)
 561:                     ? that.propertyType == null
 562:                     : propertyType.equals(that.propertyType);
 563: 
 564:             // compares the property editor classes and checks the case where both are null
 565:             boolean samePropertyEditorClass =
 566:                 (propertyEditorClass == null)
 567:                     ? that.propertyEditorClass == null
 568:                     : propertyEditorClass.equals(that.propertyEditorClass);
 569: 
 570:             // compares the flags for equality
 571:             boolean sameFlags =
 572:                 bound == that.bound && constrained == that.constrained;
 573: 
 574:             // compares the read methods and checks the case where both are null
 575:             boolean sameReadMethod =
 576:                 (getMethod == null)
 577:                     ? that.getMethod == null
 578:                     : getMethod.equals(that.getMethod);
 579: 
 580:             boolean sameWriteMethod =
 581:                 (setMethod == null)
 582:                     ? that.setMethod == null
 583:                     : setMethod.equals(that.setMethod);
 584: 
 585:             return samePropertyType
 586:                 && sameFlags
 587:                 && sameReadMethod
 588:                 && sameWriteMethod
 589:                 && samePropertyEditorClass;
 590:         }
 591:         else
 592:         {
 593:             return false;
 594:         }
 595:         
 596:     }
 597: 
 598: }