KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > beans > PropertyDescriptor


1 /*
2  * @(#)PropertyDescriptor.java 1.72 04/05/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.beans;
9
10 import java.lang.ref.Reference JavaDoc;
11
12 import java.lang.reflect.Method JavaDoc;
13 import java.lang.reflect.Constructor JavaDoc;
14
15 /**
16  * A PropertyDescriptor describes one property that a Java Bean
17  * exports via a pair of accessor methods.
18  */

19 public class PropertyDescriptor extends FeatureDescriptor JavaDoc {
20
21     private Reference JavaDoc propertyTypeRef;
22     private Reference JavaDoc readMethodRef;
23     private Reference JavaDoc writeMethodRef;
24     private Reference JavaDoc propertyEditorClassRef;
25
26     private boolean bound;
27     private boolean constrained;
28
29     // The base name of the method name which will be prefixed with the
30
// read and write method. If name == "foo" then the baseName is "Foo"
31
private String JavaDoc baseName;
32
33     private String JavaDoc writeMethodName;
34     private String JavaDoc readMethodName;
35
36     /**
37      * Constructs a PropertyDescriptor for a property that follows
38      * the standard Java convention by having getFoo and setFoo
39      * accessor methods. Thus if the argument name is "fred", it will
40      * assume that the writer method is "setFred" and the reader method
41      * is "getFred" (or "isFred" for a boolean property). Note that the
42      * property name should start with a lower case character, which will
43      * be capitalized in the method names.
44      *
45      * @param propertyName The programmatic name of the property.
46      * @param beanClass The Class object for the target bean. For
47      * example sun.beans.OurButton.class.
48      * @exception IntrospectionException if an exception occurs during
49      * introspection.
50      */

51     public PropertyDescriptor(String JavaDoc propertyName, Class JavaDoc<?> beanClass)
52         throws IntrospectionException JavaDoc {
53     this(propertyName, beanClass,
54          "is" + capitalize(propertyName),
55          "set" + capitalize(propertyName));
56     }
57
58     /**
59      * This constructor takes the name of a simple property, and method
60      * names for reading and writing the property.
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      * @param readMethodName The name of the method used for reading the property
66      * value. May be null if the property is write-only.
67      * @param writeMethodName The name of the method used for writing the property
68      * value. May be null if the property is read-only.
69      * @exception IntrospectionException if an exception occurs during
70      * introspection.
71      */

72     public PropertyDescriptor(String JavaDoc propertyName, Class JavaDoc<?> beanClass,
73         String JavaDoc readMethodName, String JavaDoc writeMethodName)
74         throws IntrospectionException JavaDoc {
75     if (beanClass == null) {
76         throw new IntrospectionException JavaDoc("Target Bean class is null");
77     }
78     if (propertyName == null || propertyName.length() == 0) {
79         throw new IntrospectionException JavaDoc("bad property name");
80     }
81     if ("".equals(readMethodName) || "".equals(writeMethodName)) {
82         throw new IntrospectionException JavaDoc("read or write method name should not be the empty string");
83     }
84     setName(propertyName);
85     setClass0(beanClass);
86
87     this.readMethodName = readMethodName;
88     if (readMethodName != null && getReadMethod() == null) {
89         throw new IntrospectionException JavaDoc("Method not found: " + readMethodName);
90     }
91     this.writeMethodName = writeMethodName;
92     if (writeMethodName != null && getWriteMethod() == null) {
93         throw new IntrospectionException JavaDoc("Method not found: " + writeMethodName);
94     }
95     
96     }
97
98     /**
99      * This constructor takes the name of a simple property, and Method
100      * objects for reading and writing the property.
101      *
102      * @param propertyName The programmatic name of the property.
103      * @param readMethod The method used for reading the property value.
104      * May be null if the property is write-only.
105      * @param writeMethod The method used for writing the property value.
106      * May be null if the property is read-only.
107      * @exception IntrospectionException if an exception occurs during
108      * introspection.
109      */

110     public PropertyDescriptor(String JavaDoc propertyName, Method JavaDoc readMethod, Method JavaDoc writeMethod)
111         throws IntrospectionException JavaDoc {
112     if (propertyName == null || propertyName.length() == 0) {
113         throw new IntrospectionException JavaDoc("bad property name");
114     }
115     setName(propertyName);
116     setReadMethod(readMethod);
117     setWriteMethod(writeMethod);
118     }
119
120     /**
121      * Gets the Class object for the property.
122      *
123      * @return The Java type info for the property. Note that
124      * the "Class" object may describe a built-in Java type such as "int".
125      * The result may be "null" if this is an indexed property that
126      * does not support non-indexed access.
127      * <p>
128      * This is the type that will be returned by the ReadMethod.
129      */

130     public synchronized Class JavaDoc<?> getPropertyType() {
131     Class JavaDoc type = getPropertyType0();
132     if (type == null) {
133         try {
134         type = findPropertyType(getReadMethod(), getWriteMethod());
135         setPropertyType(type);
136         } catch (IntrospectionException JavaDoc ex) {
137         // Fall
138
}
139     }
140     return type;
141     }
142
143     private void setPropertyType(Class JavaDoc type) {
144     propertyTypeRef = createReference(type);
145     }
146
147     private Class JavaDoc getPropertyType0() {
148     return (Class JavaDoc)getObject(propertyTypeRef);
149     }
150
151     /**
152      * Gets the method that should be used to read the property value.
153      *
154      * @return The method that should be used to read the property value.
155      * May return null if the property can't be read.
156      */

157     public synchronized Method JavaDoc getReadMethod() {
158     Method JavaDoc readMethod = getReadMethod0();
159     if (readMethod == null) {
160         Class JavaDoc cls = getClass0();
161         if (cls == null || (readMethodName == null && readMethodRef == null)) {
162         // The read method was explicitly set to null.
163
return null;
164         }
165         if (readMethodName == null) {
166         Class JavaDoc type = getPropertyType0();
167         if (type == boolean.class || type == null) {
168             readMethodName = "is" + getBaseName();
169         } else {
170             readMethodName = "get" + getBaseName();
171         }
172         }
173         
174         // Since there can be multiple write methods but only one getter
175
// method, find the getter method first so that you know what the
176
// property type is. For booleans, there can be "is" and "get"
177
// methods. If an "is" method exists, this is the official
178
// reader method so look for this one first.
179
readMethod = Introspector.findMethod(cls, readMethodName, 0);
180         if (readMethod == null) {
181         readMethodName = "get" + getBaseName();
182         readMethod = Introspector.findMethod(cls, readMethodName, 0);
183         }
184         try {
185         setReadMethod(readMethod);
186         } catch (IntrospectionException JavaDoc ex) {
187         // fall
188
}
189     }
190     return readMethod;
191     }
192
193     /**
194      * Sets the method that should be used to read the property value.
195      *
196      * @param readMethod The new read method.
197      */

198     public synchronized void setReadMethod(Method JavaDoc readMethod)
199                 throws IntrospectionException JavaDoc {
200     if (readMethod == null) {
201         readMethodName = null;
202         readMethodRef = null;
203         return;
204     }
205     // The property type is determined by the read method.
206
setPropertyType(findPropertyType(readMethod, getWriteMethod0()));
207     setClass0(readMethod.getDeclaringClass());
208
209     readMethodName = readMethod.getName();
210     readMethodRef = createReference(readMethod, true);
211     }
212
213     /**
214      * Gets the method that should be used to write the property value.
215      *
216      * @return The method that should be used to write the property value.
217      * May return null if the property can't be written.
218      */

219     public synchronized Method JavaDoc getWriteMethod() {
220     Method JavaDoc writeMethod = getWriteMethod0();
221     if (writeMethod == null) {
222         Class JavaDoc cls = getClass0();
223         if (cls == null || (writeMethodName == null && writeMethodRef == null)) {
224         // The write method was explicitly set to null.
225
return null;
226         }
227
228         // We need the type to fetch the correct method.
229
Class JavaDoc type = getPropertyType0();
230         if (type == null) {
231         try {
232             // Can't use getPropertyType since it will lead to recursive loop.
233
type = findPropertyType(getReadMethod(), null);
234             setPropertyType(type);
235         } catch (IntrospectionException JavaDoc ex) {
236             // Without the correct property type we can't be guaranteed
237
// to find the correct method.
238
return null;
239         }
240         }
241             
242         if (writeMethodName == null) {
243         writeMethodName = "set" + getBaseName();
244         }
245
246         writeMethod = Introspector.findMethod(cls, writeMethodName, 1,
247               (type == null) ? null : new Class JavaDoc[] { type });
248         try {
249         setWriteMethod(writeMethod);
250         } catch (IntrospectionException JavaDoc ex) {
251         // fall through
252
}
253     }
254     return writeMethod;
255     }
256
257     /**
258      * Sets the method that should be used to write the property value.
259      *
260      * @param writeMethod The new write method.
261      */

262     public synchronized void setWriteMethod(Method JavaDoc writeMethod)
263                 throws IntrospectionException JavaDoc {
264     if (writeMethod == null) {
265         writeMethodName = null;
266         writeMethodRef = null;
267         return;
268     }
269     // Set the property type - which validates the method
270
setPropertyType(findPropertyType(getReadMethod(), writeMethod));
271     setClass0(writeMethod.getDeclaringClass());
272
273     writeMethodName = writeMethod.getName();
274     writeMethodRef = createReference(writeMethod, true);
275
276     }
277
278     private Method JavaDoc getReadMethod0() {
279     return (Method JavaDoc)getObject(readMethodRef);
280     }
281
282     private Method JavaDoc getWriteMethod0() {
283     return (Method JavaDoc)getObject(writeMethodRef);
284     }
285
286     /**
287      * Overridden to ensure that a super class doesn't take precedent
288      */

289     void setClass0(Class JavaDoc clz) {
290     if (getClass0() != null && clz.isAssignableFrom(getClass0())) {
291         // dont replace a subclass with a superclass
292
return;
293     }
294     super.setClass0(clz);
295     }
296
297     /**
298      * Updates to "bound" properties will cause a "PropertyChange" event to
299      * get fired when the property is changed.
300      *
301      * @return True if this is a bound property.
302      */

303     public boolean isBound() {
304     return bound;
305     }
306
307     /**
308      * Updates to "bound" properties will cause a "PropertyChange" event to
309      * get fired when the property is changed.
310      *
311      * @param bound True if this is a bound property.
312      */

313     public void setBound(boolean bound) {
314     this.bound = bound;
315     }
316
317     /**
318      * Attempted updates to "Constrained" properties will cause a "VetoableChange"
319      * event to get fired when the property is changed.
320      *
321      * @return True if this is a constrained property.
322      */

323     public boolean isConstrained() {
324     return constrained;
325     }
326
327     /**
328      * Attempted updates to "Constrained" properties will cause a "VetoableChange"
329      * event to get fired when the property is changed.
330      *
331      * @param constrained True if this is a constrained property.
332      */

333     public void setConstrained(boolean constrained) {
334     this.constrained = constrained;
335     }
336
337
338     /**
339      * Normally PropertyEditors will be found using the PropertyEditorManager.
340      * However if for some reason you want to associate a particular
341      * PropertyEditor with a given property, then you can do it with
342      * this method.
343      *
344      * @param propertyEditorClass The Class for the desired PropertyEditor.
345      */

346     public void setPropertyEditorClass(Class JavaDoc<?> propertyEditorClass) {
347     propertyEditorClassRef = createReference(propertyEditorClass);
348     }
349
350     /**
351      * Gets any explicit PropertyEditor Class that has been registered
352      * for this property.
353      *
354      * @return Any explicit PropertyEditor Class that has been registered
355      * for this property. Normally this will return "null",
356      * indicating that no special editor has been registered,
357      * so the PropertyEditorManager should be used to locate
358      * a suitable PropertyEditor.
359      */

360     public Class JavaDoc<?> getPropertyEditorClass() {
361     return (Class JavaDoc)getObject(propertyEditorClassRef);
362     }
363
364     /**
365      * Constructs an instance of a property editor using the current
366      * property editor class.
367      * <p>
368      * If the property editor class has a public constructor that takes an
369      * Object argument then it will be invoked using the bean parameter
370      * as the argument. Otherwise, the default constructor will be invoked.
371      *
372      * @param bean the source object
373      * @return a property editor instance or null if a property editor has
374      * not been defined or cannot be created
375      * @since 1.5
376      */

377     public PropertyEditor JavaDoc createPropertyEditor(Object JavaDoc bean) {
378     Object JavaDoc editor = null;
379
380     Class JavaDoc cls = getPropertyEditorClass();
381     if (cls != null) {
382         Constructor JavaDoc ctor = null;
383         if (bean != null) {
384         try {
385             ctor = cls.getConstructor(new Class JavaDoc[] { Object JavaDoc.class });
386         } catch (Exception JavaDoc ex) {
387             // Fall through
388
}
389         }
390         try {
391         if (ctor == null) {
392             editor = cls.newInstance();
393         } else {
394             editor = ctor.newInstance(new Object JavaDoc[] { bean });
395         }
396         } catch (Exception JavaDoc ex) {
397         // A serious error has occured.
398
// Proably due to an invalid property editor.
399
throw new RuntimeException JavaDoc("PropertyEditor not instantiated",
400                        ex);
401         }
402     }
403     return (PropertyEditor JavaDoc)editor;
404     }
405
406
407     /**
408      * Compares this <code>PropertyDescriptor</code> against the specified object.
409      * Returns true if the objects are the same. Two <code>PropertyDescriptor</code>s
410      * are the same if the read, write, property types, property editor and
411      * flags are equivalent.
412      *
413      * @since 1.4
414      */

415     public boolean equals(Object JavaDoc obj) {
416     if (this == obj) {
417         return true;
418     }
419     if (obj != null && obj instanceof PropertyDescriptor JavaDoc) {
420         PropertyDescriptor JavaDoc other = (PropertyDescriptor JavaDoc)obj;
421         Method JavaDoc otherReadMethod = other.getReadMethod();
422         Method JavaDoc otherWriteMethod = other.getWriteMethod();
423
424         if (!compareMethods(getReadMethod(), otherReadMethod)) {
425         return false;
426         }
427
428         if (!compareMethods(getWriteMethod(), otherWriteMethod)) {
429         return false;
430         }
431
432         if (getPropertyType() == other.getPropertyType() &&
433         getPropertyEditorClass() == other.getPropertyEditorClass() &&
434         bound == other.isBound() && constrained == other.isConstrained() &&
435         writeMethodName == other.writeMethodName &&
436         readMethodName == other.readMethodName) {
437         return true;
438         }
439     }
440     return false;
441     }
442
443     /**
444      * Package private helper method for Descriptor .equals methods.
445      *
446      * @param a first method to compare
447      * @param b second method to compare
448      * @return boolean to indicate that the methods are equivalent
449      */

450     boolean compareMethods(Method JavaDoc a, Method JavaDoc b) {
451     // Note: perhaps this should be a protected method in FeatureDescriptor
452
if ((a == null) != (b == null)) {
453         return false;
454     }
455
456     if (a != null && b != null) {
457         if (!a.equals(b)) {
458         return false;
459         }
460     }
461     return true;
462     }
463
464     /**
465      * Package-private constructor.
466      * Merge two property descriptors. Where they conflict, give the
467      * second argument (y) priority over the first argument (x).
468      *
469      * @param x The first (lower priority) PropertyDescriptor
470      * @param y The second (higher priority) PropertyDescriptor
471      */

472     PropertyDescriptor(PropertyDescriptor JavaDoc x, PropertyDescriptor JavaDoc y) {
473     super(x,y);
474
475     if (y.baseName != null) {
476         baseName = y.baseName;
477     } else {
478         baseName = x.baseName;
479     }
480
481     if (y.readMethodName != null) {
482         readMethodName = y.readMethodName;
483     } else {
484         readMethodName = x.readMethodName;
485     }
486
487     if (y.writeMethodName != null) {
488         writeMethodName = y.writeMethodName;
489     } else {
490         writeMethodName = x.writeMethodName;
491     }
492
493     if (y.propertyTypeRef != null) {
494         propertyTypeRef = y.propertyTypeRef;
495     } else {
496         propertyTypeRef = x.propertyTypeRef;
497     }
498
499     // Figure out the merged read method.
500
Method JavaDoc xr = x.getReadMethod();
501     Method JavaDoc yr = y.getReadMethod();
502
503     // Normally give priority to y's readMethod.
504
try {
505         if (yr != null && yr.getDeclaringClass() == getClass0()) {
506         setReadMethod(yr);
507         } else {
508         setReadMethod(xr);
509         }
510     } catch (IntrospectionException JavaDoc ex) {
511         // fall through
512
}
513         
514     // However, if both x and y reference read methods in the same class,
515
// give priority to a boolean "is" method over a boolean "get" method.
516
if (xr != null && yr != null &&
517            xr.getDeclaringClass() == yr.getDeclaringClass() &&
518            xr.getReturnType() == boolean.class &&
519            yr.getReturnType() == boolean.class &&
520            xr.getName().indexOf("is") == 0 &&
521            yr.getName().indexOf("get") == 0) {
522         try {
523         setReadMethod(xr);
524         } catch (IntrospectionException JavaDoc ex) {
525         // fall through
526
}
527     }
528
529     Method JavaDoc xw = x.getWriteMethod();
530     Method JavaDoc yw = y.getWriteMethod();
531
532     try {
533         if (yw != null && yw.getDeclaringClass() == getClass0()) {
534         setWriteMethod(yw);
535         } else {
536         setWriteMethod(xw);
537         }
538     } catch (IntrospectionException JavaDoc ex) {
539         // Fall through
540
}
541
542     if (y.getPropertyEditorClass() != null) {
543         setPropertyEditorClass(y.getPropertyEditorClass());
544     } else {
545         setPropertyEditorClass(x.getPropertyEditorClass());
546     }
547     
548
549     bound = x.bound | y.bound;
550     constrained = x.constrained | y.constrained;
551     }
552
553     /*
554      * Package-private dup constructor.
555      * This must isolate the new object from any changes to the old object.
556      */

557     PropertyDescriptor(PropertyDescriptor JavaDoc old) {
558     super(old);
559     propertyTypeRef = old.propertyTypeRef;
560     readMethodRef = old.readMethodRef;
561     writeMethodRef = old.writeMethodRef;
562     propertyEditorClassRef = old.propertyEditorClassRef;
563
564     writeMethodName = old.writeMethodName;
565     readMethodName = old.readMethodName;
566     baseName = old.baseName;
567
568     bound = old.bound;
569     constrained = old.constrained;
570     }
571
572     /**
573      * Returns the property type that corresponds to the read and write method.
574      * The type precedence is given to the readMethod.
575      *
576      * @return the type of the property descriptor or null if both
577      * read and write methods are null.
578      * @throws IntrospectionException if the read or write method is invalid
579      */

580     private Class JavaDoc findPropertyType(Method JavaDoc readMethod, Method JavaDoc writeMethod)
581     throws IntrospectionException JavaDoc {
582     Class JavaDoc propertyType = null;
583     try {
584         if (readMethod != null) {
585         Class JavaDoc[] params = readMethod.getParameterTypes();
586         if (params.length != 0) {
587             throw new IntrospectionException JavaDoc("bad read method arg count: "
588                              + readMethod);
589         }
590         propertyType = readMethod.getReturnType();
591         if (propertyType == Void.TYPE) {
592             throw new IntrospectionException JavaDoc("read method " +
593                     readMethod.getName() + " returns void");
594         }
595         }
596         if (writeMethod != null) {
597         Class JavaDoc params[] = writeMethod.getParameterTypes();
598         if (params.length != 1) {
599             throw new IntrospectionException JavaDoc("bad write method arg count: "
600                              + writeMethod);
601         }
602         if (propertyType != null && propertyType != params[0]) {
603             throw new IntrospectionException JavaDoc("type mismatch between read and write methods");
604         }
605         propertyType = params[0];
606         }
607     } catch (IntrospectionException JavaDoc ex) {
608         throw ex;
609     }
610     return propertyType;
611     }
612
613
614     /**
615      * Returns a hash code value for the object.
616      * See {@link java.lang.Object#hashCode} for a complete description.
617      *
618      * @return a hash code value for this object.
619      * @since 1.5
620      */

621     public int hashCode() {
622     int result = 7;
623
624     result = 37 * result + ((getPropertyType() == null) ? 0 :
625                 getPropertyType().hashCode());
626     result = 37 * result + ((getReadMethod() == null) ? 0 :
627                 getReadMethod().hashCode());
628     result = 37 * result + ((getWriteMethod() == null) ? 0 :
629                 getWriteMethod().hashCode());
630     result = 37 * result + ((getPropertyEditorClass() == null) ? 0 :
631                 getPropertyEditorClass().hashCode());
632     result = 37 * result + ((writeMethodName == null) ? 0 :
633                 writeMethodName.hashCode());
634     result = 37 * result + ((readMethodName == null) ? 0 :
635                 readMethodName.hashCode());
636     result = 37 * result + getName().hashCode();
637     result = 37 * result + ((bound == false) ? 0 : 1);
638     result = 37 * result + ((constrained == false) ? 0 : 1);
639
640     return result;
641     }
642
643     // Calculate once since capitalize() is expensive.
644
String JavaDoc getBaseName() {
645     if (baseName == null) {
646         baseName = capitalize(getName());
647     }
648     return baseName;
649     }
650
651     /*
652     public String toString() {
653     String message = "name=" + getName();
654     message += ", class=" + getClass0();
655     message += ", type=" + getPropertyType();
656
657     message += ", writeMethod=";
658     message += writeMethodName;
659
660     message += ", readMethod=";
661     message += readMethodName;
662
663     message += ", bound=" + bound;
664     message += ", constrained=" + constrained;
665
666     return message;
667     }
668     */

669 }
670
Popular Tags