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