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 final MethodRef readMethodRef = new MethodRef(); 40 private final MethodRef writeMethodRef = new MethodRef(); 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 = this.readMethodRef.get(); 207 if (readMethod == null) { 208 Class<?> cls = getClass0(); 209 if (cls == null || (readMethodName == null && !this.readMethodRef.isSet())) { 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 this.readMethodRef.set(readMethod); 251 if (readMethod == null) { 252 readMethodName = null; 253 return; 254 } 255 // The property type is determined by the read method. 256 setPropertyType(findPropertyType(readMethod, this.writeMethodRef.get())); 257 setClass0(readMethod.getDeclaringClass()); 258 259 readMethodName = readMethod.getName(); 260 setTransient(readMethod.getAnnotation(Transient.class)); 261 } 262 263 /** 264 * Gets the method that should be used to write the property value. 265 * 266 * @return The method that should be used to write the property value. 267 * May return null if the property can't be written. 268 */ 269 public synchronized Method getWriteMethod() { 270 Method writeMethod = this.writeMethodRef.get(); 271 if (writeMethod == null) { 272 Class<?> cls = getClass0(); 273 if (cls == null || (writeMethodName == null && !this.writeMethodRef.isSet())) { 274 // The write method was explicitly set to null. 275 return null; 276 } 277 278 // We need the type to fetch the correct method. 279 Class<?> type = getPropertyType0(); 280 if (type == null) { 281 try { 282 // Can't use getPropertyType since it will lead to recursive loop. 283 type = findPropertyType(getReadMethod(), null); 284 setPropertyType(type); 285 } catch (IntrospectionException ex) { 286 // Without the correct property type we can't be guaranteed 287 // to find the correct method. 288 return null; 289 } 290 } 291 292 if (writeMethodName == null) { 293 writeMethodName = Introspector.SET_PREFIX + getBaseName(); 294 } 295 296 Class<?>[] args = (type == null) ? null : new Class<?>[] { type }; 297 writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args); 298 if (writeMethod != null) { 299 if (!writeMethod.getReturnType().equals(void.class)) { 300 writeMethod = null; 301 } 302 } 303 try { 304 setWriteMethod(writeMethod); 305 } catch (IntrospectionException ex) { 306 // fall through 307 } 308 } 309 return writeMethod; 310 } 311 312 /** 313 * Sets the method that should be used to write the property value. 314 * 315 * @param writeMethod The new write method. 316 * @throws IntrospectionException if the write method is invalid 317 */ 318 public synchronized void setWriteMethod(Method writeMethod) 319 throws IntrospectionException { 320 this.writeMethodRef.set(writeMethod); 321 if (writeMethod == null) { 322 writeMethodName = null; 323 return; 324 } 325 // Set the property type - which validates the method 326 setPropertyType(findPropertyType(getReadMethod(), writeMethod)); 327 setClass0(writeMethod.getDeclaringClass()); 328 329 writeMethodName = writeMethod.getName(); 330 setTransient(writeMethod.getAnnotation(Transient.class)); 331 } 332 333 /** 334 * Overridden to ensure that a super class doesn't take precedent 335 */ 336 void setClass0(Class<?> clz) { 337 if (getClass0() != null && clz.isAssignableFrom(getClass0())) { 338 // don't replace a subclass with a superclass 339 return; 340 } 341 super.setClass0(clz); 342 } 343 344 /** 345 * Updates to "bound" properties will cause a "PropertyChange" event to 346 * get fired when the property is changed. 347 * 348 * @return True if this is a bound property. 349 */ 350 public boolean isBound() { 351 return bound; 352 } 353 354 /** 355 * Updates to "bound" properties will cause a "PropertyChange" event to 356 * get fired when the property is changed. 357 * 358 * @param bound True if this is a bound property. 359 */ 360 public void setBound(boolean bound) { 361 this.bound = bound; 362 } 363 364 /** 365 * Attempted updates to "Constrained" properties will cause a "VetoableChange" 366 * event to get fired when the property is changed. 367 * 368 * @return True if this is a constrained property. 369 */ 370 public boolean isConstrained() { 371 return constrained; 372 } 373 374 /** 375 * Attempted updates to "Constrained" properties will cause a "VetoableChange" 376 * event to get fired when the property is changed. 377 * 378 * @param constrained True if this is a constrained property. 379 */ 380 public void setConstrained(boolean constrained) { 381 this.constrained = constrained; 382 } 383 384 385 /** 386 * Normally PropertyEditors will be found using the PropertyEditorManager. 387 * However if for some reason you want to associate a particular 388 * PropertyEditor with a given property, then you can do it with 389 * this method. 390 * 391 * @param propertyEditorClass The Class for the desired PropertyEditor. 392 */ 393 public void setPropertyEditorClass(Class<?> propertyEditorClass) { 394 this.propertyEditorClassRef = getWeakReference(propertyEditorClass); 395 } 396 397 /** 398 * Gets any explicit PropertyEditor Class that has been registered 399 * for this property. 400 * 401 * @return Any explicit PropertyEditor Class that has been registered 402 * for this property. Normally this will return "null", 403 * indicating that no special editor has been registered, 404 * so the PropertyEditorManager should be used to locate 405 * a suitable PropertyEditor. 406 */ 407 public Class<?> getPropertyEditorClass() { 408 return (this.propertyEditorClassRef != null) 409 ? this.propertyEditorClassRef.get() 410 : null; 411 } 412 413 /** 414 * Constructs an instance of a property editor using the current 415 * property editor class. 416 * <p> 417 * If the property editor class has a public constructor that takes an 418 * Object argument then it will be invoked using the bean parameter 419 * as the argument. Otherwise, the default constructor will be invoked. 420 * 421 * @param bean the source object 422 * @return a property editor instance or null if a property editor has 423 * not been defined or cannot be created 424 * @since 1.5 425 */ 426 public PropertyEditor createPropertyEditor(Object bean) { 427 Object editor = null; 428 429 Class<?> cls = getPropertyEditorClass(); 430 if (cls != null) { 431 Constructor<?> ctor = null; 432 if (bean != null) { 433 try { 434 ctor = cls.getConstructor(new Class<?>[] { Object.class }); 435 } catch (Exception ex) { 436 // Fall through 437 } 438 } 439 try { 440 if (ctor == null) { 441 editor = cls.newInstance(); 442 } else { 443 editor = ctor.newInstance(new Object[] { bean }); 444 } 445 } catch (Exception ex) { 446 // Fall through 447 } 448 } 449 return (PropertyEditor)editor; 450 } 451 452 453 /** 454 * Compares this <code>PropertyDescriptor</code> against the specified object. 455 * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s 456 * are the same if the read, write, property types, property editor and 457 * flags are equivalent. 458 * 459 * @since 1.4 460 */ 461 public boolean equals(Object obj) { 462 if (this == obj) { 463 return true; 464 } 465 if (obj != null && obj instanceof PropertyDescriptor) { 466 PropertyDescriptor other = (PropertyDescriptor)obj; 467 Method otherReadMethod = other.getReadMethod(); 468 Method otherWriteMethod = other.getWriteMethod(); 469 470 if (!compareMethods(getReadMethod(), otherReadMethod)) { 471 return false; 472 } 473 474 if (!compareMethods(getWriteMethod(), otherWriteMethod)) { 475 return false; 476 } 477 478 if (getPropertyType() == other.getPropertyType() && 479 getPropertyEditorClass() == other.getPropertyEditorClass() && 480 bound == other.isBound() && constrained == other.isConstrained() && 481 writeMethodName == other.writeMethodName && 482 readMethodName == other.readMethodName) { 483 return true; 484 } 485 } 486 return false; 487 } 488 489 /** 490 * Package private helper method for Descriptor .equals methods. 491 * 492 * @param a first method to compare 493 * @param b second method to compare 494 * @return boolean to indicate that the methods are equivalent 495 */ 496 boolean compareMethods(Method a, Method b) { 497 // Note: perhaps this should be a protected method in FeatureDescriptor 498 if ((a == null) != (b == null)) { 499 return false; 500 } 501 502 if (a != null && b != null) { 503 if (!a.equals(b)) { 504 return false; 505 } 506 } 507 return true; 508 } 509 510 /** 511 * Package-private constructor. 512 * Merge two property descriptors. Where they conflict, give the 513 * second argument (y) priority over the first argument (x). 514 * 515 * @param x The first (lower priority) PropertyDescriptor 516 * @param y The second (higher priority) PropertyDescriptor 517 */ 518 PropertyDescriptor(PropertyDescriptor x, PropertyDescriptor y) { 519 super(x,y); 520 521 if (y.baseName != null) { 522 baseName = y.baseName; 523 } else { 524 baseName = x.baseName; 525 } 526 527 if (y.readMethodName != null) { 528 readMethodName = y.readMethodName; 529 } else { 530 readMethodName = x.readMethodName; 531 } 532 533 if (y.writeMethodName != null) { 534 writeMethodName = y.writeMethodName; 535 } else { 536 writeMethodName = x.writeMethodName; 537 } 538 539 if (y.propertyTypeRef != null) { 540 propertyTypeRef = y.propertyTypeRef; 541 } else { 542 propertyTypeRef = x.propertyTypeRef; 543 } 544 545 // Figure out the merged read method. 546 Method xr = x.getReadMethod(); 547 Method yr = y.getReadMethod(); 548 549 // Normally give priority to y's readMethod. 550 try { 551 if (isAssignable(xr, yr)) { 552 setReadMethod(yr); 553 } else { 554 setReadMethod(xr); 555 } 556 } catch (IntrospectionException ex) { 557 // fall through 558 } 559 560 // However, if both x and y reference read methods in the same class, 561 // give priority to a boolean "is" method over a boolean "get" method. 562 if (xr != null && yr != null && 563 xr.getDeclaringClass() == yr.getDeclaringClass() && 564 getReturnType(getClass0(), xr) == boolean.class && 565 getReturnType(getClass0(), yr) == boolean.class && 566 xr.getName().indexOf(Introspector.IS_PREFIX) == 0 && 567 yr.getName().indexOf(Introspector.GET_PREFIX) == 0) { 568 try { 569 setReadMethod(xr); 570 } catch (IntrospectionException ex) { 571 // fall through 572 } 573 } 574 575 Method xw = x.getWriteMethod(); 576 Method yw = y.getWriteMethod(); 577 578 try { 579 if (yw != null) { 580 setWriteMethod(yw); 581 } else { 582 setWriteMethod(xw); 583 } 584 } catch (IntrospectionException ex) { 585 // Fall through 586 } 587 588 if (y.getPropertyEditorClass() != null) { 589 setPropertyEditorClass(y.getPropertyEditorClass()); 590 } else { 591 setPropertyEditorClass(x.getPropertyEditorClass()); 592 } 593 594 595 bound = x.bound | y.bound; 596 constrained = x.constrained | y.constrained; 597 } 598 599 /* 600 * Package-private dup constructor. 601 * This must isolate the new object from any changes to the old object. 602 */ 603 PropertyDescriptor(PropertyDescriptor old) { 604 super(old); 605 propertyTypeRef = old.propertyTypeRef; 606 this.readMethodRef.set(old.readMethodRef.get()); 607 this.writeMethodRef.set(old.writeMethodRef.get()); 608 propertyEditorClassRef = old.propertyEditorClassRef; 609 610 writeMethodName = old.writeMethodName; 611 readMethodName = old.readMethodName; 612 baseName = old.baseName; 613 614 bound = old.bound; 615 constrained = old.constrained; 616 } 617 618 void updateGenericsFor(Class<?> type) { 619 setClass0(type); 620 try { 621 setPropertyType(findPropertyType(this.readMethodRef.get(), this.writeMethodRef.get())); 622 } 623 catch (IntrospectionException exception) { 624 setPropertyType(null); 625 } 626 } 627 628 /** 629 * Returns the property type that corresponds to the read and write method. 630 * The type precedence is given to the readMethod. 631 * 632 * @return the type of the property descriptor or null if both 633 * read and write methods are null. 634 * @throws IntrospectionException if the read or write method is invalid 635 */ 636 private Class<?> findPropertyType(Method readMethod, Method writeMethod) 637 throws IntrospectionException { 638 Class<?> propertyType = null; 639 try { 640 if (readMethod != null) { 641 Class<?>[] params = getParameterTypes(getClass0(), readMethod); 642 if (params.length != 0) { 643 throw new IntrospectionException("bad read method arg count: " 644 + readMethod); 645 } 646 propertyType = getReturnType(getClass0(), readMethod); 647 if (propertyType == Void.TYPE) { 648 throw new IntrospectionException("read method " + 649 readMethod.getName() + " returns void"); 650 } 651 } 652 if (writeMethod != null) { 653 Class<?>[] params = getParameterTypes(getClass0(), writeMethod); 654 if (params.length != 1) { 655 throw new IntrospectionException("bad write method arg count: " 656 + writeMethod); 657 } 658 if (propertyType != null && !params[0].isAssignableFrom(propertyType)) { 659 throw new IntrospectionException("type mismatch between read and write methods"); 660 } 661 propertyType = params[0]; 662 } 663 } catch (IntrospectionException ex) { 664 throw ex; 665 } 666 return propertyType; 667 } 668 669 670 /** 671 * Returns a hash code value for the object. 672 * See {@link java.lang.Object#hashCode} for a complete description. 673 * 674 * @return a hash code value for this object. 675 * @since 1.5 676 */ 677 public int hashCode() { 678 int result = 7; 679 680 result = 37 * result + ((getPropertyType() == null) ? 0 : 681 getPropertyType().hashCode()); 682 result = 37 * result + ((getReadMethod() == null) ? 0 : 683 getReadMethod().hashCode()); 684 result = 37 * result + ((getWriteMethod() == null) ? 0 : 685 getWriteMethod().hashCode()); 686 result = 37 * result + ((getPropertyEditorClass() == null) ? 0 : 687 getPropertyEditorClass().hashCode()); 688 result = 37 * result + ((writeMethodName == null) ? 0 : 689 writeMethodName.hashCode()); 690 result = 37 * result + ((readMethodName == null) ? 0 : 691 readMethodName.hashCode()); 692 result = 37 * result + getName().hashCode(); 693 result = 37 * result + ((bound == false) ? 0 : 1); 694 result = 37 * result + ((constrained == false) ? 0 : 1); 695 696 return result; 697 } 698 699 // Calculate once since capitalize() is expensive. 700 String getBaseName() { 701 if (baseName == null) { 702 baseName = NameGenerator.capitalize(getName()); 703 } 704 return baseName; 705 } 706 707 void appendTo(StringBuilder sb) { 708 appendTo(sb, "bound", this.bound); 709 appendTo(sb, "constrained", this.constrained); 710 appendTo(sb, "propertyEditorClass", this.propertyEditorClassRef); 711 appendTo(sb, "propertyType", this.propertyTypeRef); 712 appendTo(sb, "readMethod", this.readMethodRef.get()); 713 appendTo(sb, "writeMethod", this.writeMethodRef.get()); 714 } 715 716 private boolean isAssignable(Method m1, Method m2) { 717 if (m1 == null) { 718 return true; // choose second method 719 } 720 if (m2 == null) { 721 return false; // choose first method 722 } 723 if (!m1.getName().equals(m2.getName())) { 724 return true; // choose second method by default 725 } 726 Class<?> type1 = m1.getDeclaringClass(); 727 Class<?> type2 = m2.getDeclaringClass(); 728 if (!type1.isAssignableFrom(type2)) { 729 return false; // choose first method: it declared later 730 } 731 type1 = getReturnType(getClass0(), m1); 732 type2 = getReturnType(getClass0(), m2); 733 if (!type1.isAssignableFrom(type2)) { 734 return false; // choose first method: it overrides return type 735 } 736 Class<?>[] args1 = getParameterTypes(getClass0(), m1); 737 Class<?>[] args2 = getParameterTypes(getClass0(), m2); 738 if (args1.length != args2.length) { 739 return true; // choose second method by default 740 } 741 for (int i = 0; i < args1.length; i++) { 742 if (!args1[i].isAssignableFrom(args2[i])) { 743 return false; // choose first method: it overrides parameter 744 } 745 } 746 return true; // choose second method 747 } 748 }