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