KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > inversoft > beans > BaseBeanProperty


1 /*
2  * Copyright (c) 2003, Inversoft
3  *
4  * This software is distribuable under the GNU Lesser General Public License.
5  * For more information visit gnu.org.
6  */

7 package com.inversoft.beans;
8
9
10 import java.util.ArrayList JavaDoc;
11 import java.util.Iterator JavaDoc;
12 import java.util.List JavaDoc;
13
14 import com.inversoft.util.ReflectionException;
15 import com.inversoft.util.ReflectionTools;
16 import com.inversoft.util.typeconverter.TypeConversionException;
17 import com.inversoft.util.typeconverter.TypeConverter;
18 import com.inversoft.util.typeconverter.TypeConverterRegistry;
19
20
21 /**
22  * This class is the base class for the BeanProperty and the
23  * NestedBeanProperty classes. It provides the standard methods
24  * used by both classes and the standard properties
25  *
26  * @author Brian Pontarelli
27  */

28 public abstract class BaseBeanProperty {
29
30     //-------------------------------------------------------------------------
31
// The types of events that get fired from the bean property
32
//-------------------------------------------------------------------------
33

34
35     /**
36      * Used to denote a get event when sub-classes are firing events
37      */

38     protected static final int GET = 0;
39
40     /**
41      * Used to denote a set event when sub-classes are firing events
42      */

43     protected static final int SET = 1;
44
45     /**
46      * Used to denote a conversion event is about to take place when sub-classes
47      * are firing events
48      */

49     protected static final int PRE_CONVERT = 2;
50
51     /**
52      * Used to denote a conversion event has just taken place when sub-classes
53      * are firing events
54      */

55     protected static final int POST_CONVERT = 3;
56
57     /**
58      * Used to denote a conversion event has failed to take place when sub-classes
59      * are firing events
60      */

61     protected static final int BAD_CONVERT = 4;
62
63
64     /**
65      * Holds the name of the property that this bean property describes
66      */

67     protected String JavaDoc propertyName;
68
69     /**
70      * Holds the Class that the property is contain in
71      */

72     protected Class JavaDoc beanClass;
73
74     /**
75      * Holds the Class type of the property that this bean property describes
76      */

77     protected Class JavaDoc propertyType;
78
79     /**
80      * This holds the list of property listeners for the bean property
81      */

82     protected List JavaDoc propertyListeners;
83
84     /**
85      * This holds the list of conversion listeners for the bean property
86      */

87     protected List JavaDoc conversionListeners;
88
89
90     /**
91      * Empty default constructor which can be called by sub-classes that are not
92      * going to use the propertyName and beanClass or need to delay the
93      * initialization of those members until later. This only sets up the list
94      * for the property listeners. This constructor does not make a template
95      * method call to initialize. Sub-classes must make this call inside
96      * constructors or elsewhere if they wish to use the initialize method
97      * for initialization.
98      */

99     protected BaseBeanProperty() {
100         propertyListeners = new ArrayList JavaDoc();
101         conversionListeners = new ArrayList JavaDoc();
102     }
103
104     /**
105      * Constuctor with initial properties. This constructor should be called
106      * by the sub-classes if they want to setup the propertyName and beanClass
107      * members during construction. This constructor also makes a template
108      * method call to {@link #initialize() initialize()}. The initialize method
109      * must be implemented my sub-classes and should do all the initialization
110      * work. This constructor simply calls the default constructor, sets up the
111      * propertyName and beanClass members and then calls to initialize
112      */

113     protected BaseBeanProperty(String JavaDoc propertyName, Class JavaDoc beanClass) throws BeanException {
114         this();
115         this.propertyName = propertyName;
116         this.beanClass = beanClass;
117
118         // Template Method
119
initialize();
120     }
121
122     /**
123      * Constuctor with initial properties. This constructor should be called by
124      * the sub-classes if they want to setup the propertyName and beanClass members
125      * during construction. This constructor also makes a template method call to
126      * {@link #initialize() initialize()}. The initialize method must be implemented
127      * my sub-classes and should do all the initialization work. This constructor
128      * simply calls the default constructor, sets up the propertyName and beanClass
129      * members and then calls to initialize. This method takes the fully qualified
130      * name of the bean class to be used.
131      */

132     protected BaseBeanProperty(String JavaDoc propertyName, String JavaDoc beanClass) throws BeanException {
133         this();
134         this.propertyName = propertyName;
135
136         try {
137             this.beanClass = Class.forName(beanClass);
138         } catch (ClassNotFoundException JavaDoc cnfe) {
139             throw new BeanException(cnfe.getMessage(), cnfe);
140         }
141
142         // Template Method
143
initialize();
144     }
145
146     /**
147      * Sub-classes should put all the initialization logic in this method NOT in
148      * constructors. The reason for this is that constructors can chain effectively
149      * without causing un-wanted initialization to occur.<p>
150      * For example, if the the BeanProperty constructor set up the read and write
151      * Method objects for itself and the IndexedBeanProperty wanted to chain constructors
152      * an error would occur because the indexed properties do not have the same signatures
153      * as the bean properties. This would cause the BeanProperty constructor that did
154      * the initialization to throw a BeanException stating that the property didn't
155      * exist even though it did.<p>
156      * For this reason, the initialize method should be used to do all the initialization.
157      *
158      * @throws BeanException If there was any errors during initialization
159      */

160     abstract protected void initialize() throws BeanException;
161
162     /**
163      * Gets the name of the property. If this is a simple BeanProperty, than the
164      * name is just the name of the property.<p>
165      * <b>For BeanProperty</b><br>
166      * getName() -> name<p>
167      * <b> For IndexedBeanProperty</b><br>
168      * getFoo(indices) -> foo<p>
169      * <b>For NestedBeanProperty</b><br>
170      * getFoo().getBar().getName() -> foo.bar.name<p>
171      * New sub-classes can override this method, but it is advisable to use the
172      * default version because it returns the property name that was used during
173      * construction.<p>
174      * <b>NOTE:</b> If a sub-classes uses the default constructor and does not make
175      * use of the propertyName member of this class, then this method should be
176      * overridden (if applicable).
177      *
178      * @return The name of the property
179      */

180     public String JavaDoc getPropertyName() {
181         return propertyName;
182     }
183
184     /**
185      * <p>
186      * Gets the type of the property, which may be null depending on the type of
187      * property. If this BaseBeanProperty is actually a {@link BeanProperty
188      * BeanProperty} or an {@link IndexedBeanProperty IndexedBeanProperty} then
189      * this method returns the properties defined type. This does not return the
190      * runtime type of the property.
191      * </p>
192      *
193      * <p>
194      * For example:<br>
195      * public String getName() {} -> java.lang.String
196      * </p>
197      *
198      * <p>
199      * This method does not work for nested properties because each property in
200      * the nesting can have a different runtime type. Therefore, this method
201      * attempts to determine the declared type. However, in some cases, this will
202      * be impossible.
203      * </p>
204      *
205      * <p>
206      * Here's an example of a declared property whose type can be retrieved.<br/>
207      * getFoo().getBar().getName()<br>
208      * public Bean1 getFoo()<br>
209      * public Bean2 getBar()<br>
210      * public String getName()<br>
211      * The property type is found to be a String.
212      * </p>
213      *
214      * <p>
215      * Here's an example of a declared property whose type can NOT be retrieved.<br/>
216      * getFoo().getBar().getArray()[1].getIndexed(2).getName()<br>
217      * public Object getFoo()<br>
218      * public Object getBar()<br>
219      * public Object[] getArray()<br>
220      * public Object getIndexed(int indices)<br>
221      * public String getName()<br>
222      * The property type can not be determined until runtime so that we can figure
223      * out what the actually objects returned from all those methods are.
224      * </p>
225      *
226      * @return The type of the property
227      */

228     public Class JavaDoc getPropertyType() {
229         return propertyType;
230     }
231
232     /**
233      * Gets the full name of the property. This is most useful for sub-classes because it
234      * allows them to specify a custom name for the property rather than using the propertyName
235      * member variable. The default implementation here simply returns the propertyName
236      * member variable.
237      *
238      * @return The full name of the property, which is usually just the propertyName
239      * member variable, but sub-classes can override this method to change the
240      * name however they see fit.
241      */

242     public String JavaDoc getFullName() {
243         return propertyName;
244     }
245
246     /**
247      * Returns the class of the bean that this property is contained in
248      */

249     public Class JavaDoc getBeanClass() {
250         return beanClass;
251     }
252
253     /**
254      * Creates an instance of the class that this BeanProperty's property is contained
255      * in.
256      *
257      * @return A new instance of the class
258      * @throws BeanException If there was a problem instantiating the class
259      */

260     public Object JavaDoc instantiate() throws BeanException {
261         try {
262             return ReflectionTools.instantiate(beanClass);
263         } catch (ReflectionException re) {
264             throw new BeanException(re);
265         }
266     }
267
268     /**
269      * Adds a property listener to the list of listeners for this property. This
270      * method is not thread safe. If you need to have a thread safe implementation,
271      * please use one of the Synchronized sub-classes.
272      *
273      * @param listener The new property listener to add to the list
274      */

275     public void addPropertyListener(PropertyListener listener) {
276         propertyListeners.add(listener);
277     }
278
279     /**
280      * Removes the given property listener from the list of property listeners. This
281      * method is not thread safe. If you need to have a thread safe implementation,
282      * please use one of the Synchronized sub-classes.
283      *
284      * @param listener The property listener to remove from the list
285      */

286     public void removePropertyListener(PropertyListener listener) {
287         propertyListeners.remove(listener);
288     }
289
290     /**
291      * Returns an iterator for the list of property listeners. This method is not
292      * thread safe. If you need to have a thread safe implementation, please use
293      * one of the Synchronized sub-classes.
294      *
295      * @return An interator for the list of property listeners
296      */

297     public Iterator JavaDoc getPropertyListeners() {
298         return propertyListeners.iterator();
299     }
300
301     /**
302      * Returns true if there are any property listeners for this property. This
303      * method is not thread safe. If you need to have a thread safe implementation,
304      * please use one of the Synchronized sub-classes.
305      *
306      * @return True or false depending on if there are property listeners or not
307      */

308     public boolean hasPropertyListeners() {
309         return !propertyListeners.isEmpty();
310     }
311
312     /**
313      * Adds a conversion listener to the list of listeners for this property. This
314      * method is not thread safe. If you need to have a thread safe implementation,
315      * please use one of the Synchronized sub-classes.
316      *
317      * @param listener The new conversion listener to add to the list
318      */

319     public void addConversionListener(ConversionListener listener) {
320         conversionListeners.add(listener);
321     }
322
323     /**
324      * Removes the given conversion listener from the list of conversion listeners.
325      * This method is not thread safe. If you need to have a thread safe implementation,
326      * please use one of the Synchronized sub-classes.
327      *
328      * @param listener The conversion listener to remove from the list
329      */

330     public void removeConversionListener(ConversionListener listener) {
331         conversionListeners.remove(listener);
332     }
333
334     /**
335      * Returns an iterator for the list of conversion listeners. This method is not
336      * thread safe. If you need to have a thread safe implementation, pleas use one
337      * of the Synchronized sub-classes.
338      */

339     public Iterator JavaDoc getConversionListeners() {
340         return conversionListeners.iterator();
341     }
342
343     /**
344      * Returns true if there are any conversion listeners for this property. This
345      * method is not thread safe. If you need to have a thread safe implementation,
346      * please use one of the Synchronized sub-classes.
347      */

348     public boolean hasConversionListeners() {
349         return !conversionListeners.isEmpty();
350     }
351
352     /**
353      * Gets the the property value. Sub-classes must implement this method.
354      * @param bean The bean instance to set the property on
355      * @throws BeanException If there was an error getting the JavaBean property or
356      * the getter/is method threw a checked Exception
357      */

358     abstract public Object JavaDoc getPropertyValue(final Object JavaDoc bean) throws BeanException;
359
360     /**
361      * Sets the the property value. Sub-classes must implement this method.
362      *
363      * @param bean The bean instance to set the property on
364      * @param value The value to set on the bean
365      * @param convert Determines whether or not the value should be converted
366      * to the correct parameter type for the property set method
367      * @throws BeanException If there was an error setting the JavaBean property or
368      * the setter method threw a checked Exception
369      * @throws TypeConversionException If there was an error attempting to auto
370      * convert the value being set
371      */

372     abstract public void setPropertyValue(final Object JavaDoc bean, Object JavaDoc value,
373             final boolean convert)
374     throws BeanException, TypeConversionException;
375
376     /**
377      * Sets the value of the bean property to the given value. This value is set
378      * without doing any conversion. This method is the same as calling the other
379      * setPropertyValue method with the convert parameter set to false
380      *
381      * @param bean The instance of the JavaBean to set the property on
382      * @param value The value to set the property to
383      * @throws BeanException If there was an error setting the JavaBean property or
384      * the setter method threw a checked Exception
385      */

386     public void setPropertyValue(final Object JavaDoc bean, Object JavaDoc value) throws BeanException {
387         try {
388             setPropertyValue(bean, value, false);
389         } catch (TypeConversionException tce) {
390             assert false : "FATAL: should never throw TypeConversionException" +
391                 " because this method does no conversion";
392         }
393     }
394
395     /**
396      * Converts the given value parameter (parameter) to a type that is accepted
397      * by the set method of this property. This method attempts to convert the value
398      * regardless of the value being null. However, this method short circuits and
399      * returns the value unchanged if value is runtime assignable to the type
400      * of this BaseBeanProperty.
401      *
402      * @param value The value object to convert
403      * @param bean The bean object that this parameter object is being converted for
404      * and is usually used when firing events
405      * @return The value parameter converted to the correct type
406      * @throws TypeConversionException If there was a problem converting the parameter
407      */

408     protected Object JavaDoc convertParameter(final Object JavaDoc value, final Object JavaDoc bean,
409             Class JavaDoc type)
410     throws TypeConversionException {
411
412         Object JavaDoc newValue = value;
413
414         // The converter does this, but pre-emptively checking these conditions will
415
// speed up conversion times
416
if (!type.isInstance(value)) {
417
418             if (hasConversionListeners()) {
419                 fireConversionEvent(PRE_CONVERT, value, newValue, bean);
420             }
421
422             TypeConverter converter = TypeConverterRegistry.lookup(type);
423             if (converter == null) {
424                 throw new TypeConversionException("No type converter found for the type: "
425                     + type.getName());
426             }
427
428             try {
429                 newValue = converter.convert(value, type);
430             } catch (TypeConversionException tce) {
431
432                 if (hasConversionListeners()) {
433                     fireConversionEvent(BAD_CONVERT, value, newValue, bean);
434                 }
435
436                 throw tce;
437             }
438         }
439
440         if (hasConversionListeners()) {
441             fireConversionEvent(POST_CONVERT, value, newValue, bean);
442         }
443
444         return newValue;
445     }
446
447     /**
448      * Fires off a property event using the type constants of this class and the
449      * parameters given. Most of these parameters are used by all bean properties,
450      * except the indices parameter which is only used with indexed properties.
451      *
452      * @param type The type of event to fire. Must be either the GET or SET
453      * constants of this class
454      * @param oldValue The old value of the property, which is the same as the newValue
455      * for all events except the SET event
456      * @param newValue The new value of the property, which is normally the current
457      * value of the property except for the SET event
458      * @param bean The bean object that the event is occurring for
459      * @param index The indices if this is an IndexedBeanProperty (use -1 for all other
460      * types of properties)
461      * @throws IllegalArgumentException If the type is not a valid property event type
462      * and there are listeners
463      */

464     protected void firePropertyEvent(final int type, final Object JavaDoc oldValue,
465             final Object JavaDoc newValue, final Object JavaDoc bean, final Object JavaDoc index)
466     throws BeanException {
467
468         // Loop through the property listeners and fire off the events
469
Iterator JavaDoc iter = propertyListeners.iterator();
470         PropertyEvent event = new PropertyEvent(this, oldValue, newValue, bean, index);
471         PropertyListener listener;
472
473         while (iter.hasNext()) {
474             listener = (PropertyListener) iter.next();
475
476             switch (type) {
477             case GET:
478                 listener.handleGet(event);
479                 break;
480             case SET:
481                 listener.handleSet(event);
482                 break;
483             default:
484                 throw new IllegalArgumentException JavaDoc("Invalid event type");
485             }
486         }
487     }
488
489     /**
490      * Fires off a conversion event using the type constants of this class and the
491      * parameters given.
492      *
493      * @param type The type of event to fire. Must be either the PRE_CONVERT,
494      * POST_CONVERT or BAD_CONVERT constants of this class
495      * @param oldValue The old value of the property, which is the parameter value
496      * before conversion
497      * @param newValue The new value of the property, which is the parameter value
498      * after conversion unless this is called for the PRE_CONVERT and
499      * BAD_CONVERT events and then it should be the same as the oldValue
500      * @param bean The bean object that the event is occurring for
501      * @throws IllegalArgumentException If the type is not a valid conversion event type
502      * and there are listeners
503      */

504     protected void fireConversionEvent(final int type, final Object JavaDoc oldValue, final Object JavaDoc newValue,
505             final Object JavaDoc bean) {
506
507         // Loop through the conversion listeners and fire off the events
508
Iterator JavaDoc iter = conversionListeners.iterator();
509         ConversionEvent event = new ConversionEvent(this, oldValue, newValue, bean);
510         ConversionListener listener;
511
512         while (iter.hasNext()) {
513             listener = (ConversionListener) iter.next();
514
515             switch (type) {
516             case PRE_CONVERT:
517                 listener.handlePreConversion(event);
518                 break;
519             case POST_CONVERT:
520                 listener.handlePostConversion(event);
521                 break;
522             case BAD_CONVERT:
523                 listener.handleFailedConversion(event);
524                 break;
525             default:
526                 throw new IllegalArgumentException JavaDoc("Invalid event type");
527             }
528         }
529     }
530 }
531
Popular Tags