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