GNU Classpath (0.17) | ||
Frames | No Frames |
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><propertyType> <getMethodName>()</CODE></LI> 53: ** <LI>A set method must have signature 54: ** <CODE>void <setMethodName>(<propertyType>)</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<name>()</CODE> 83: ** (or, optionally, if the property is boolean, 84: ** <CODE>is<name>()</CODE>) and 85: ** <CODE>set<name>()</CODE> in class 86: ** <CODE><beanClass></CODE>, where <name> 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: }
GNU Classpath (0.17) |