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