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