1 /* 2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.beans; 27 28 import java.lang.ref.Reference; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.Constructor; 31 32 /** 33 * A PropertyDescriptor describes one property that a Java Bean 34 * exports via a pair of accessor methods. 35 */ 36 public class PropertyDescriptor extends FeatureDescriptor { 37 38 private Reference<? extends Class<?>> propertyTypeRef; 39 private Reference<Method> readMethodRef; 40 private Reference<Method> writeMethodRef; 41 private Reference<? extends Class<?>> propertyEditorClassRef; 42 43 private boolean bound; 44 private boolean constrained; 45 46 // The base name of the method name which will be prefixed with the 47 // read and write method. If name == "foo" then the baseName is "Foo" 48 private String baseName; 49 50 private String writeMethodName; 51 private String readMethodName; 52 53 /** 54 * Constructs a PropertyDescriptor for a property that follows 55 * the standard Java convention by having getFoo and setFoo 56 * accessor methods. Thus if the argument name is "fred", it will 57 * assume that the writer method is "setFred" and the reader method 58 * is "getFred" (or "isFred" for a boolean property). Note that the 59 * property name should start with a lower case character, which will 60 * be capitalized in the method names. 61 * 62 * @param propertyName The programmatic name of the property. 63 * @param beanClass The Class object for the target bean. For 64 * example sun.beans.OurButton.class. 65 * @exception IntrospectionException if an exception occurs during 66 * introspection. 67 */ 68 public PropertyDescriptor(String propertyName, Class<?> beanClass) 69 throws IntrospectionException { 70 this(propertyName, beanClass, 71 Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName), 72 Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName)); 73 } 74 75 /** 76 * This constructor takes the name of a simple property, and method 77 * names for reading and writing the property. 78 * 79 * @param propertyName The programmatic name of the property. 80 * @param beanClass The Class object for the target bean. For 81 * example sun.beans.OurButton.class. 82 * @param readMethodName The name of the method used for reading the property 83 * value. May be null if the property is write-only. 84 * @param writeMethodName The name of the method used for writing the property 85 * value. May be null if the property is read-only. 86 * @exception IntrospectionException if an exception occurs during 87 * introspection. 88 */ 89 public PropertyDescriptor(String propertyName, Class<?> beanClass, 90 String readMethodName, String writeMethodName) 91 throws IntrospectionException { 92 if (beanClass == null) { 93 throw new IntrospectionException("Target Bean class is null"); 94 } 95 if (propertyName == null || propertyName.length() == 0) { 96 throw new IntrospectionException("bad property name"); 97 } 98 if ("".equals(readMethodName) || "".equals(writeMethodName)) { 99 throw new IntrospectionException("read or write method name should not be the empty string"); 100 } 101 setName(propertyName); 102 setClass0(beanClass); 103 104 this.readMethodName = readMethodName; 105 if (readMethodName != null && getReadMethod() == null) { 106 throw new IntrospectionException("Method not found: " + readMethodName); 107 } 108 this.writeMethodName = writeMethodName; 109 if (writeMethodName != null && getWriteMethod() == null) { 110 throw new IntrospectionException("Method not found: " + writeMethodName); 111 } 112 // If this class or one of its base classes allow PropertyChangeListener, 113 // then we assume that any properties we discover are "bound". 114 // See Introspector.getTargetPropertyInfo() method. 115 Class[] args = { PropertyChangeListener.class }; 116 this.bound = null != Introspector.findMethod(beanClass, "addPropertyChangeListener", args.length, args); 117 } 118 119 /** 120 * This constructor takes the name of a simple property, and Method 121 * objects for reading and writing the property. 122 * 123 * @param propertyName The programmatic name of the property. 124 * @param readMethod The method used for reading the property value. 125 * May be null if the property is write-only. 126 * @param writeMethod The method used for writing the property value. 127 * May be null if the property is read-only. 128 * @exception IntrospectionException if an exception occurs during 129 * introspection. 130 */ 131 public PropertyDescriptor(String propertyName, Method readMethod, Method writeMethod) 132 throws IntrospectionException { 133 if (propertyName == null || propertyName.length() == 0) { 134 throw new IntrospectionException("bad property name"); 135 } 136 setName(propertyName); 137 setReadMethod(readMethod); 138 setWriteMethod(writeMethod); 139 } 140 141 /** 142 * Creates <code>PropertyDescriptor</code> for the specified bean 143 * with the specified name and methods to read/write the property value. 144 * 145 * @param bean the type of the target bean 146 * @param base the base name of the property (the rest of the method name) 147 * @param read the method used for reading the property value 148 * @param write the method used for writing the property value 149 * @exception IntrospectionException if an exception occurs during introspection 150 * 151 * @since 1.7 152 */ 153 PropertyDescriptor(Class<?> bean, String base, Method read, Method write) throws IntrospectionException { 154 if (bean == null) { 155 throw new IntrospectionException("Target Bean class is null"); 156 } 157 setClass0(bean); 158 setName(Introspector.decapitalize(base)); 159 setReadMethod(read); 160 setWriteMethod(write); 161 this.baseName = base; 162 } 163 164 /** 165 * Returns the Java type info for the property. 166 * Note that the {@code Class} object may describe 167 * primitive Java types such as {@code int}. 168 * This type is returned by the read method 169 * or is used as the parameter type of the write method. 170 * Returns {@code null} if the type is an indexed property 171 * that does not support non-indexed access. 172 * 173 * @return the {@code Class} object that represents the Java type info, 174 * or {@code null} if the type cannot be determined 175 */ 176 public synchronized Class<?> getPropertyType() { 177 Class<?> type = getPropertyType0(); 178 if (type == null) { 179 try { 180 type = findPropertyType(getReadMethod(), getWriteMethod()); 181 setPropertyType(type); 182 } catch (IntrospectionException ex) { 183 // Fall 184 } 185 } 186 return type; 187 } 188 189 private void setPropertyType(Class<?> type) { 190 this.propertyTypeRef = getWeakReference(type); 191 } 192 193 private Class<?> getPropertyType0() { 194 return (this.propertyTypeRef != null) 195 ? this.propertyTypeRef.get() 196 : null; 197 } 198 199 /** 200 * Gets the method that should be used to read the property value. 201 * 202 * @return The method that should be used to read the property value. 203 * May return null if the property can't be read. 204 */ 205 public synchronized Method getReadMethod() { 206 Method readMethod = getReadMethod0(); 207 if (readMethod == null) { 208 Class<?> cls = getClass0(); 209 if (cls == null || (readMethodName == null && readMethodRef == null)) { 210 // The read method was explicitly set to null. 211 return null; 212 } 213 String nextMethodName = Introspector.GET_PREFIX + getBaseName(); 214 if (readMethodName == null) { 215 Class<?> type = getPropertyType0(); 216 if (type == boolean.class || type == null) { 217 readMethodName = Introspector.IS_PREFIX + getBaseName(); 218 } else { 219 readMethodName = nextMethodName; 220 } 221 } 222 223 // Since there can be multiple write methods but only one getter 224 // method, find the getter method first so that you know what the 225 // property type is. For booleans, there can be "is" and "get" 226 // methods. If an "is" method exists, this is the official 227 // reader method so look for this one first. 228 readMethod = Introspector.findMethod(cls, readMethodName, 0); 229 if ((readMethod == null) && !readMethodName.equals(nextMethodName)) { 230 readMethodName = nextMethodName; 231 readMethod = Introspector.findMethod(cls, readMethodName, 0); 232 } 233 try { 234 setReadMethod(readMethod); 235 } catch (IntrospectionException ex) { 236 // fall 237 } 238 } 239 return readMethod; 240 } 241 242 /** 243 * Sets the method that should be used to read the property value. 244 * 245 * @param readMethod The new read method. 246 * @throws IntrospectionException if the read method is invalid 247 */ 248 public synchronized void setReadMethod(Method readMethod) 249 throws IntrospectionException { 250 if (readMethod == null) { 251 readMethodName = null; 252 readMethodRef = null; 253 return; 254 } 255 // The property type is determined by the read method. 256 setPropertyType(findPropertyType(readMethod, getWriteMethod0())); 257 setClass0(readMethod.getDeclaringClass()); 258 259 readMethodName = readMethod.getName(); 260 this.readMethodRef = getSoftReference(readMethod); 261 setTransient(readMethod.getAnnotation(Transient.class)); 262 } 263 264 /** 265 * Gets the method that should be used to write the property value. 266 * 267 * @return The method that should be used to write the property value. 268 * May return null if the property can't be written. 269 */ 270 public synchronized Method getWriteMethod() { 271 Method writeMethod = getWriteMethod0(); 272 if (writeMethod == null) { 273 Class<?> cls = getClass0(); 274 if (cls == null || (writeMethodName == null && writeMethodRef == null)) { 275 // The write method was explicitly set to null. 276 return null; 277 } 278 279 // We need the type to fetch the correct method. 280 Class<?> type = getPropertyType0(); 281 if (type == null) { 282 try { 283 // Can't use getPropertyType since it will lead to recursive loop. 284 type = findPropertyType(getReadMethod(), null); 285 setPropertyType(type); 286 } catch (IntrospectionException ex) { 287 // Without the correct property type we can't be guaranteed 288 // to find the correct method. 289 return null; 290 } 291 } 292 293 if (writeMethodName == null) { 294 writeMethodName = Introspector.SET_PREFIX + getBaseName(); 295 } 296 297 Class<?>[] args = (type == null) ? null : new Class<?>[] { type }; 298 writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args); 299 if (writeMethod != null) { 300 if (!writeMethod.getReturnType().equals(void.class)) { 301 writeMethod = null; 302 } 303 } 304 try { 305 setWriteMethod(writeMethod); 306 } catch (IntrospectionException ex) { 307 // fall through 308 } 309 } 310 return writeMethod; 311 } 312 313 /** 314 * Sets the method that should be used to write the property value. 315 * 316 * @param writeMethod The new write method. 317 * @throws IntrospectionException if the write method is invalid 318 */ 319 public synchronized void setWriteMethod(Method writeMethod) 320 throws IntrospectionException { 321 if (writeMethod == null) { 322 writeMethodName = null; 323 writeMethodRef = null; 324 return; 325 } 326 // Set the property type - which validates the method 327 setPropertyType(findPropertyType(getReadMethod(), writeMethod)); 328 setClass0(writeMethod.getDeclaringClass()); 329 330 writeMethodName = writeMethod.getName(); 331 this.writeMethodRef = getSoftReference(writeMethod); 332 setTransient(writeMethod.getAnnotation(Transient.class)); 333 } 334 335 private Method getReadMethod0() { 336 return (this.readMethodRef != null) 337 ? this.readMethodRef.get() 338 : null; 339 } 340 341 private Method getWriteMethod0() { 342 return (this.writeMethodRef != null) 343 ? this.writeMethodRef.get() 344 : null; 345 } 346 347 /** 348 * Overridden to ensure that a super class doesn't take precedent 349 */ 350 void setClass0(Class<?> clz) { 351 if (getClass0() != null && clz.isAssignableFrom(getClass0())) { 352 // dont replace a subclass with a superclass 353 return; 354 } 355 super.setClass0(clz); 356 } 357 358 /** 359 * Updates to "bound" properties will cause a "PropertyChange" event to 360 * get fired when the property is changed. 361 * 362 * @return True if this is a bound property. 363 */ 364 public boolean isBound() { 365 return bound; 366 } 367 368 /** 369 * Updates to "bound" properties will cause a "PropertyChange" event to 370 * get fired when the property is changed. 371 * 372 * @param bound True if this is a bound property. 373 */ 374 public void setBound(boolean bound) { 375 this.bound = bound; 376 } 377 378 /** 379 * Attempted updates to "Constrained" properties will cause a "VetoableChange" 380 * event to get fired when the property is changed. 381 * 382 * @return True if this is a constrained property. 383 */ 384 public boolean isConstrained() { 385 return constrained; 386 } 387 388 /** 389 * Attempted updates to "Constrained" properties will cause a "VetoableChange" 390 * event to get fired when the property is changed. 391 * 392 * @param constrained True if this is a constrained property. 393 */ 394 public void setConstrained(boolean constrained) { 395 this.constrained = constrained; 396 } 397 398 399 /** 400 * Normally PropertyEditors will be found using the PropertyEditorManager. 401 * However if for some reason you want to associate a particular 402 * PropertyEditor with a given property, then you can do it with 403 * this method. 404 * 405 * @param propertyEditorClass The Class for the desired PropertyEditor. 406 */ 407 public void setPropertyEditorClass(Class<?> propertyEditorClass) { 408 this.propertyEditorClassRef = getWeakReference(propertyEditorClass); 409 } 410 411 /** 412 * Gets any explicit PropertyEditor Class that has been registered 413 * for this property. 414 * 415 * @return Any explicit PropertyEditor Class that has been registered 416 * for this property. Normally this will return "null", 417 * indicating that no special editor has been registered, 418 * so the PropertyEditorManager should be used to locate 419 * a suitable PropertyEditor. 420 */ 421 public Class<?> getPropertyEditorClass() { 422 return (this.propertyEditorClassRef != null) 423 ? this.propertyEditorClassRef.get() 424 : null; 425 } 426 427 /** 428 * Constructs an instance of a property editor using the current 429 * property editor class. 430 * <p> 431 * If the property editor class has a public constructor that takes an 432 * Object argument then it will be invoked using the bean parameter 433 * as the argument. Otherwise, the default constructor will be invoked. 434 * 435 * @param bean the source object 436 * @return a property editor instance or null if a property editor has 437 * not been defined or cannot be created 438 * @since 1.5 439 */ 440 public PropertyEditor createPropertyEditor(Object bean) { 441 Object editor = null; 442 443 Class<?> cls = getPropertyEditorClass(); 444 if (cls != null) { 445 Constructor<?> ctor = null; 446 if (bean != null) { 447 try { 448 ctor = cls.getConstructor(new Class<?>[] { Object.class }); 449 } catch (Exception ex) { 450 // Fall through 451 } 452 } 453 try { 454 if (ctor == null) { 455 editor = cls.newInstance(); 456 } else { 457 editor = ctor.newInstance(new Object[] { bean }); 458 } 459 } catch (Exception ex) { 460 // Fall through 461 } 462 } 463 return (PropertyEditor)editor; 464 } 465 466 467 /** 468 * Compares this <code>PropertyDescriptor</code> against the specified object. 469 * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s 470 * are the same if the read, write, property types, property editor and 471 * flags are equivalent. 472 * 473 * @since 1.4 474 */ 475 public boolean equals(Object obj) { 476 if (this == obj) { 477 return true; 478 } 479 if (obj != null && obj instanceof PropertyDescriptor) { 480 PropertyDescriptor other = (PropertyDescriptor)obj; 481 Method otherReadMethod = other.getReadMethod(); 482 Method otherWriteMethod = other.getWriteMethod(); 483 484 if (!compareMethods(getReadMethod(), otherReadMethod)) { 485 return false; 486 } 487 488 if (!compareMethods(getWriteMethod(), otherWriteMethod)) { 489 return false; 490 } 491 492 if (getPropertyType() == other.getPropertyType() && 493 getPropertyEditorClass() == other.getPropertyEditorClass() && 494 bound == other.isBound() && constrained == other.isConstrained() && 495 writeMethodName == other.writeMethodName && 496 readMethodName == other.readMethodName) { 497 return true; 498 } 499 } 500 return false; 501 } 502 503 /** 504 * Package private helper method for Descriptor .equals methods. 505 * 506 * @param a first method to compare 507 * @param b second method to compare 508 * @return boolean to indicate that the methods are equivalent 509 */ 510 boolean compareMethods(Method a, Method b) { 511 // Note: perhaps this should be a protected method in FeatureDescriptor 512 if ((a == null) != (b == null)) { 513 return false; 514 } 515 516 if (a != null && b != null) { 517 if (!a.equals(b)) { 518 return false; 519 } 520 } 521 return true; 522 } 523 524 /** 525 * Package-private constructor. 526 * Merge two property descriptors. Where they conflict, give the 527 * second argument (y) priority over the first argument (x). 528 * 529 * @param x The first (lower priority) PropertyDescriptor 530 * @param y The second (higher priority) PropertyDescriptor 531 */ 532 PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) { 533 super(x,y); 534 535 if (y.baseName != null) { 536 baseName = y.baseName; 537 } else { 538 baseName = x.baseName; 539 } 540 541 if (y.readMethodName != null) { 542 readMethodName = y.readMethodName; 543 } else { 544 readMethodName = x.readMethodName; 545 } 546 547 if (y.writeMethodName != null) { 548 writeMethodName = y.writeMethodName; 549 } else { 550 writeMethodName = x.writeMethodName; 551 } 552 553 if (y.propertyTypeRef != null) { 554 propertyTypeRef = y.propertyTypeRef; 555 } else { 556 propertyTypeRef = x.propertyTypeRef; 557 } 558 559 // Figure out the merged read method. 560 Method xr = x.getReadMethod(); 561 Method yr = y.getReadMethod(); 562 563 // Normally give priority to y's readMethod. 564 try { 565 if (isAssignable(xr, yr)) { 566 setReadMethod(yr); 567 } else { 568 setReadMethod(xr); 569 } 570 } catch (IntrospectionException ex) { 571 // fall through 572 } 573 574 // However, if both x and y reference read methods in the same class, 575 // give priority to a boolean "is" method over a boolean "get" method. 576 if (xr != null && yr != null && 577 xr.getDeclaringClass() == yr.getDeclaringClass() && 578 getReturnType(getClass0(), xr) == boolean.class && 579 getReturnType(getClass0(), yr) == boolean.class && 580 xr.getName().indexOf(Introspector.IS_PREFIX) == 0 && 581 yr.getName().indexOf(Introspector.GET_PREFIX) == 0) { 582 try { 583 setReadMethod(xr); 584 } catch (IntrospectionException ex) { 585 // fall through 586 } 587 } 588 589 Method xw = x.getWriteMethod(); 590 Method yw = y.getWriteMethod(); 591 592 try { 593 if (yw != null) { 594 setWriteMethod(yw); 595 } else { 596 setWriteMethod(xw); 597 } 598 } catch (IntrospectionException ex) { 599 // Fall through 600 } 601 602 if (y.getPropertyEditorClass() != null) { 603 setPropertyEditorClass(y.getPropertyEditorClass()); 604 } else { 605 setPropertyEditorClass(x.getPropertyEditorClass()); 606 } 607 608 609 bound = x.bound | y.bound; 610 constrained = x.constrained | y.constrained; 611 } 612 613 /* 614 * Package-private dup constructor. 615 * This must isolate the new object from any changes to the old object. 616 */ 617 PropertyDescriptor(PropertyDescriptor old) { 618 super(old); 619 propertyTypeRef = old.propertyTypeRef; 620 readMethodRef = old.readMethodRef; 621 writeMethodRef = old.writeMethodRef; 622 propertyEditorClassRef = old.propertyEditorClassRef; 623 624 writeMethodName = old.writeMethodName; 625 readMethodName = old.readMethodName; 626 baseName = old.baseName; 627 628 bound = old.bound; 629 constrained = old.constrained; 630 } 631 632 void updateGenericsFor(Class<?> type) { 633 setClass0(type); 634 try { 635 setPropertyType(findPropertyType(getReadMethod0(), getWriteMethod0())); 636 } 637 catch (IntrospectionException exception) { 638 setPropertyType(null); 639 } 640 } 641 642 /** 643 * Returns the property type that corresponds to the read and write method. 644 * The type precedence is given to the readMethod. 645 * 646 * @return the type of the property descriptor or null if both 647 * read and write methods are null. 648 * @throws IntrospectionException if the read or write method is invalid 649 */ 650 private Class<?> findPropertyType(Method readMethod, Method writeMethod) 651 throws IntrospectionException { 652 Class<?> propertyType = null; 653 try { 654 if (readMethod != null) { 655 Class<?>[] params = getParameterTypes(getClass0(), readMethod); 656 if (params.length != 0) { 657 throw new IntrospectionException("bad read method arg count: " 658 + readMethod); 659 } 660 propertyType = getReturnType(getClass0(), readMethod); 661 if (propertyType == Void.TYPE) { 662 throw new IntrospectionException("read method " + 663 readMethod.getName() + " returns void"); 664 } 665 } 666 if (writeMethod != null) { 667 Class<?>[] params = getParameterTypes(getClass0(), writeMethod); 668 if (params.length != 1) { 669 throw new IntrospectionException("bad write method arg count: " 670 + writeMethod); 671 } 672 if (propertyType != null && !params[0].isAssignableFrom(propertyType)) { 673 throw new IntrospectionException("type mismatch between read and write methods"); 674 } 675 propertyType = params[0]; 676 } 677 } catch (IntrospectionException ex) { 678 throw ex; 679 } 680 return propertyType; 681 } 682 683 684 /** 685 * Returns a hash code value for the object. 686 * See {@link java.lang.Object#hashCode} for a complete description. 687 * 688 * @return a hash code value for this object. 689 * @since 1.5 690 */ 691 public int hashCode() { 692 int result = 7; 693 694 result = 37 * result + ((getPropertyType() == null) ? 0 : 695 getPropertyType().hashCode()); 696 result = 37 * result + ((getReadMethod() == null) ? 0 : 697 getReadMethod().hashCode()); 698 result = 37 * result + ((getWriteMethod() == null) ? 0 : 699 getWriteMethod().hashCode()); 700 result = 37 * result + ((getPropertyEditorClass() == null) ? 0 : 701 getPropertyEditorClass().hashCode()); 702 result = 37 * result + ((writeMethodName == null) ? 0 : 703 writeMethodName.hashCode()); 704 result = 37 * result + ((readMethodName == null) ? 0 : 705 readMethodName.hashCode()); 706 result = 37 * result + getName().hashCode(); 707 result = 37 * result + ((bound == false) ? 0 : 1); 708 result = 37 * result + ((constrained == false) ? 0 : 1); 709 710 return result; 711 } 712 713 // Calculate once since capitalize() is expensive. 714 String getBaseName() { 715 if (baseName == null) { 716 baseName = NameGenerator.capitalize(getName()); 717 } 718 return baseName; 719 } 720 721 void appendTo(StringBuilder sb) { 722 appendTo(sb, "bound", this.bound); 723 appendTo(sb, "constrained", this.constrained); 724 appendTo(sb, "propertyEditorClass", this.propertyEditorClassRef); 725 appendTo(sb, "propertyType", this.propertyTypeRef); 726 appendTo(sb, "readMethod", this.readMethodRef); 727 appendTo(sb, "writeMethod", this.writeMethodRef); 728 } 729 730 private boolean isAssignable(Method m1, Method m2) { 731 if (m1 == null) { 732 return true; // choose second method 733 } 734 if (m2 == null) { 735 return false; // choose first method 736 } 737 if (!m1.getName().equals(m2.getName())) { 738 return true; // choose second method by default 739 } 740 Class<?> type1 = m1.getDeclaringClass(); 741 Class<?> type2 = m2.getDeclaringClass(); 742 if (!type1.isAssignableFrom(type2)) { 743 return false; // choose first method: it declared later 744 } 745 type1 = getReturnType(getClass0(), m1); 746 type2 = getReturnType(getClass0(), m2); 747 if (!type1.isAssignableFrom(type2)) { 748 return false; // choose first method: it overrides return type 749 } 750 Class<?>[] args1 = getParameterTypes(getClass0(), m1); 751 Class<?>[] args2 = getParameterTypes(getClass0(), m2); 752 if (args1.length != args2.length) { 753 return true; // choose second method by default 754 } 755 for (int i = 0; i < args1.length; i++) { 756 if (!args1[i].isAssignableFrom(args2[i])) { 757 return false; // choose first method: it overrides parameter 758 } 759 } 760 return true; // choose second method 761 } 762 }