Source Code Cross Referenced for PropertyAdapter.java in  » Swing-Library » jgoodies-data-binding » com » jgoodies » binding » beans » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Swing Library » jgoodies data binding » com.jgoodies.binding.beans 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*
002:         * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003:         *
004:         * Redistribution and use in source and binary forms, with or without
005:         * modification, are permitted provided that the following conditions are met:
006:         *
007:         *  o Redistributions of source code must retain the above copyright notice,
008:         *    this list of conditions and the following disclaimer.
009:         *
010:         *  o Redistributions in binary form must reproduce the above copyright notice,
011:         *    this list of conditions and the following disclaimer in the documentation
012:         *    and/or other materials provided with the distribution.
013:         *
014:         *  o Neither the name of JGoodies Karsten Lentzsch nor the names of
015:         *    its contributors may be used to endorse or promote products derived
016:         *    from this software without specific prior written permission.
017:         *
018:         * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019:         * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020:         * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021:         * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022:         * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023:         * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024:         * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025:         * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026:         * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027:         * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028:         * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029:         */
030:
031:        package com.jgoodies.binding.beans;
032:
033:        import java.beans.PropertyChangeEvent;
034:        import java.beans.PropertyChangeListener;
035:        import java.beans.PropertyDescriptor;
036:        import java.beans.PropertyVetoException;
037:
038:        import com.jgoodies.binding.value.AbstractValueModel;
039:        import com.jgoodies.binding.value.ValueHolder;
040:        import com.jgoodies.binding.value.ValueModel;
041:
042:        /**
043:         * Converts a single Java Bean property into the generic ValueModel interface.
044:         * The bean property must be a single value as described by the
045:         * <a href="http://java.sun.com/products/javabeans/docs/spec.html">Java
046:         * Bean Specification</a>. See below for a comparison with the more frequently
047:         * used BeanAdapter and PresentationModel classes.<p>
048:         *
049:         * The constructors accept either a property name or a triple of
050:         * (property name, getter name, setter name). If you just specify the
051:         * property name, the adapter uses the standard Java Bean introspection
052:         * to lookup the available properties and how to read and write the
053:         * property value. In case of custom readers and writers you may
054:         * specify a custom BeanInfo class, or as a shortcut use the constructors
055:         * that accept the optional getter and setter name. If these are specified,
056:         * introspection will be bypassed and a PropertyDescriptor will be
057:         * created for the given property name, getter and setter name.<p>
058:         *
059:         * Optionally the PropertyAdapter can observe changes in <em>bound
060:         * properties</em> as described in section 7.4 of the Bean specification.
061:         * You can enable this feature by setting the constructor parameter
062:         * <code>observeChanges</code> to <code>true</code>.
063:         * If the adapter observes changes, it will fire value change events,
064:         * i.e. PropertyChangeEvents for the property <code>&quot;value&quot;</code>.
065:         * Even if you ignore property changes, you can access the adapted
066:         * property value via <code>#getValue()</code>.
067:         * It's just that you won't be notified about changes.<p>
068:         *
069:         * The PropertyAdapter provides two access styles to the target bean
070:         * that holds the adapted property: you can specify a bean directly,
071:         * or you can use a <em>bean channel</em> to access the bean indirectly.
072:         * In the latter case you specify a <code>ValueModel</code>
073:         * that holds the bean that in turn holds the adapted property.<p>
074:         *
075:         * If the adapted bean is <code>null</code> the PropertyAdapter can
076:         * neither read nor set a value. In this case <code>#getValue</code>
077:         * returns <code>null</code> and <code>#setValue</code> will silently
078:         * ignore the new value.<p>
079:         *
080:         * This adapter throws three PropertyChangeEvents if the bean changes:
081:         * <em>beforeBean</em>, <em>bean</em> and <em>afterBean</em>. This is useful
082:         * when sharing a bean channel and you must perform an operation before
083:         * or after other listeners handle a bean change. Since you cannot rely
084:         * on the order listeners will be notified, only the <em>beforeBean</em>
085:         * and <em>afterBean</em> events are guaranteed to be fired before and
086:         * after the bean change is fired.
087:         * Note that <code>#getBean()</code> returns the new bean before
088:         * any of these three PropertyChangeEvents is fired. Therefore listeners
089:         * that handle these events must use the event's old and new value
090:         * to determine the old and new bean.
091:         * The order of events fired during a bean change is:<ol>
092:         * <li>this adapter's bean channel fires a <em>value</em> change,
093:         * <li>this adapter fires a <em>beforeBean</em> change,
094:         * <li>this adapter fires the <em>bean</em> change,
095:         * <li>this adapter fires an <em>afterBean</em> change.
096:         * </ol><p>
097:         *
098:         * <strong>Note:</strong>
099:         * PropertyAdapters that observe changes have a PropertyChangeListener
100:         * registered with the target bean. Hence, a bean has a reference
101:         * to any PropertyAdapter that observes it. To avoid memory leaks
102:         * it is recommended to remove this listener if the bean lives much longer than
103:         * the PropertyAdapter, enabling the garbage collector to remove the adapter.
104:         * To do so, you can call <code>setBean(null)</code> or set the
105:         * bean channel's value to null.
106:         * As an alternative you can use event listener lists in your beans
107:         * that implement references with <code>WeakReference</code>.<p>
108:         *
109:         * Setting the bean to null has side-effects, for example the adapter fires
110:         * a change event for the bound property <em>bean</em> and other properties.
111:         * And the adpter's value may change.
112:         * However, typically this is fine and setting the bean to null
113:         * is the first choice for removing the reference from the bean to the adapter.
114:         * Another way to clear the reference from the target bean is
115:         * to call <code>#release</code>. It has no side-effects, but the adapter
116:         * must not be used anymore once #release has been called.<p>
117:         *
118:         * <strong>Constraints:</strong> If property changes shall be observed,
119:         * the bean class must support bound properties, i. e. it must provide
120:         * the following pair of methods for registration of multicast property
121:         * change event listeners:
122:         * <pre>
123:         * public void addPropertyChangeListener(PropertyChangeListener x);
124:         * public void removePropertyChangeListener(PropertyChangeListener x);
125:         * </pre>
126:         *
127:         * <strong>PropertyAdapter vs. BeanAdapter vs. PresentationModel</strong><br>
128:         * If you adapt multiple properties of the same bean, you better use
129:         * a {@link com.jgoodies.binding.beans.BeanAdapter}. The BeanAdapter
130:         * registers only a single PropertyChangeListener with the bean,
131:         * where multiple PropertyAdapters would register multiple listeners.
132:         * If you adapt bean properties for an editor, you will typically use the
133:         * {@link com.jgoodies.binding.PresentationModel}. The PresentationModel is
134:         * more powerful than the BeanAdapter. It adds support for buffered models,
135:         * and provides an extensible mechanism for observing the change state
136:         * of the bean and related objects.<p>
137:         *
138:         * <strong>Basic Examples:</strong>
139:         * <pre>
140:         * // Direct access, ignores changes
141:         * Address address = new Address()
142:         * PropertyAdapter adapter = new PropertyAdapter(address, "street");
143:         * adapter.setValue("Broadway");
144:         * System.out.println(address.getStreet());    // Prints "Broadway"
145:         * address.setStreet("Franz-Josef-Strasse");
146:         * System.out.println(adapter.getValue());     // Prints "Franz-Josef-Strasse"
147:         *
148:         *
149:         * //Direct access, observes changes
150:         * PropertyAdapter adapter = new PropertyAdapter(address, "street", true);
151:         *
152:         *
153:         * // Indirect access, ignores changes
154:         * ValueHolder addressHolder = new ValueHolder(address1);
155:         * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street");
156:         * adapter.setValue("Broadway");               // Sets the street in address1
157:         * System.out.println(address1.getValue());    // Prints "Broadway"
158:         * adapter.setBean(address2);
159:         * adapter.setValue("Robert-Koch-Strasse");    // Sets the street in address2
160:         * System.out.println(address2.getValue());    // Prints "Robert-Koch-Strasse"
161:         *
162:         *
163:         * // Indirect access, observes changes
164:         * ValueHolder addressHolder = new ValueHolder();
165:         * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street", true);
166:         * addressHolder.setValue(address1);
167:         * address1.setStreet("Broadway");
168:         * System.out.println(adapter.getValue());     // Prints "Broadway"
169:         * </pre>
170:         *
171:         * <strong>Adapter Chain Example:</strong>
172:         * <br>Builds an adapter chain from a domain model to the presentation layer.
173:         * <pre>
174:         * Country country = new Country();
175:         * country.setName("Germany");
176:         * country.setEuMember(true);
177:         *
178:         * JTextField nameField = new JTextField();
179:         * nameField.setDocument(new DocumentAdapter(
180:         *      new PropertyAdapter(country, "name", true)));
181:         *
182:         * JCheckBox euMemberBox = new JCheckBox("Is EU Member");
183:         * euMemberBox.setModel(new ToggleButtonAdapter(
184:         *      new PropertyAdapter(country, "euMember", true)));
185:         *
186:         * // Using factory methods
187:         * JTextField nameField   = Factory.createTextField(country, "name");
188:         * JCheckBox  euMemberBox = Factory.createCheckBox (country, "euMember");
189:         * euMemberBox.setText("Is EU Member");
190:         * </pre><p>
191:         *
192:         * TODO: Consider adding a feature to ensure that update notifications
193:         * are performed in the event dispatch thread. In case the adapted bean
194:         * is changed in a thread other than the event dispatch thread, such
195:         * a feature would help complying with Swing's single thread rule.
196:         * The feature could be implemented by an extended PropertyChangeSupport.<p>
197:         *
198:         * TODO: I plan to improve the support for adapting beans that do not fire
199:         * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter,
200:         * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's
201:         * internal SimplePropertyAdapter's shall be able to optionally self-fire
202:         * a PropertyChangeEvent in case the bean does not. There are several
203:         * downsides with self-firing events compared to bound bean properties.
204:         * See <a href="https://binding.dev.java.net/issues/show_bug.cgi?id=49">Issue
205:         * 49</a> for more information about the downsides.<p>
206:         *
207:         * The observeChanges constructor parameter shall be replaced by a more
208:         * fine-grained choice to not observe (former observeChanges=false),
209:         * to observe bound properties (former observeChanges=true), and a new
210:         * setting for self-firing PropertyChangeEvents if a value is set.
211:         * The latter case may be further splitted up to specify how the
212:         * self-fired PropertyChangeEvent is created:
213:         * <ol>
214:         * <li>oldValue=null, newValue=null
215:         * <li>oldValue=null, newValue=the value set
216:         * <li>oldValue=value read before the set, newValue=the value set
217:         * <li>oldValue=value read before the set, newValue=value read after the set
218:         * </ol>
219:         *
220:         * @author  Karsten Lentzsch
221:         * @version $Revision: 1.13 $
222:         *
223:         * @see     com.jgoodies.binding.beans.BeanAdapter
224:         * @see     ValueModel
225:         * @see     ValueModel#getValue()
226:         * @see     ValueModel#setValue(Object)
227:         * @see     PropertyChangeEvent
228:         * @see     PropertyChangeListener
229:         * @see     java.beans.Introspector
230:         * @see     java.beans.BeanInfo
231:         * @see     PropertyDescriptor
232:         *
233:         * @param <B>  the type of the adapted bean
234:         */
235:        public final class PropertyAdapter<B> extends AbstractValueModel {
236:
237:            /**
238:             * The property name used in the PropertyChangeEvent that is fired
239:             * before the <em>bean</em> property fires its PropertyChangeEvent.
240:             * Useful to perform an operation before listeners that handle the
241:             * bean change are notified. See also the class comment.
242:             */
243:            public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean";
244:
245:            /**
246:             * The name of the read-write bound property that holds the target bean.
247:             *
248:             * @see #getBean()
249:             * @see #setBean(Object)
250:             */
251:            public static final String PROPERTYNAME_BEAN = "bean";
252:
253:            /**
254:             * The property name used in the PropertyChangeEvent that is fired
255:             * after the <em>bean</em> property fires its PropertyChangeEvent.
256:             * Useful to perform an operation after listeners that handle the
257:             * bean change are notified. See also the class comment.
258:             */
259:            public static final String PROPERTYNAME_AFTER_BEAN = "afterBean";
260:
261:            /**
262:             * The name of the read-only bound bean property that
263:             * indicates whether one of the observed properties has changed.
264:             *
265:             * @see #isChanged()
266:             */
267:            public static final String PROPERTYNAME_CHANGED = "changed";
268:
269:            // Fields *****************************************************************
270:
271:            /**
272:             * Holds a <code>ValueModel</code> that holds the bean, that in turn
273:             * holds the adapted property.
274:             *
275:             * @see #getBean()
276:             * @see #setBean(Object)
277:             */
278:            private final ValueModel beanChannel;
279:
280:            /**
281:             * Holds the name of the adapted property.
282:             *
283:             * @see #getPropertyName()
284:             */
285:            private final String propertyName;
286:
287:            /**
288:             * Holds the optional name of the property's getter.
289:             */
290:            private final String getterName;
291:
292:            /**
293:             * Holds the optional name of the property's setter.
294:             */
295:            private final String setterName;
296:
297:            /**
298:             * Specifies whether we observe property changes and in turn
299:             * fire state changes.
300:             *
301:             * @see #getObserveChanges()
302:             */
303:            private final boolean observeChanges;
304:
305:            /**
306:             * Refers to the old bean. Used as old value if the bean changes.
307:             * Updated after a bean change in the BeanChangeHandler.
308:             */
309:            private B storedOldBean;
310:
311:            /**
312:             * Indicates whether a property in the current target been has changed.
313:             * Will be reset to <code>false</code> every time the target bean changes.
314:             *
315:             * @see #isChanged()
316:             * @see #setBean(Object)
317:             */
318:            private boolean changed = false;
319:
320:            /**
321:             * The <code>PropertyChangeListener</code> used to handle changes
322:             * in the adapted bean property. A new instance is created every time
323:             * the target bean changes.
324:             */
325:            private PropertyChangeListener propertyChangeHandler;
326:
327:            /**
328:             * Describes the property accessor; basically a getter and setter.
329:             */
330:            private PropertyDescriptor cachedPropertyDescriptor;
331:
332:            /**
333:             * Holds the bean class associated with the cached property descriptor.
334:             */
335:            private Class<?> cachedBeanClass;
336:
337:            // Instance creation ****************************************************
338:
339:            /**
340:             * Constructs a <code>PropertyAdapter</code> for the given
341:             * bean and property name; does not observe changes.
342:             *
343:             * @param bean             the bean that owns the property
344:             * @param propertyName     the name of the adapted property
345:             * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
346:             * @throws IllegalArgumentException if <code>propertyName</code> is empty
347:             */
348:            public PropertyAdapter(B bean, String propertyName) {
349:                this (bean, propertyName, false);
350:            }
351:
352:            /**
353:             * Constructs a <code>PropertyAdapter</code> for the given
354:             * bean and property name; observes changes if specified.
355:             *
356:             * @param bean             the bean that owns the property
357:             * @param propertyName     the name of the adapted property
358:             * @param observeChanges   <code>true</code> to observe changes of bound
359:             *     or constrained properties, <code>false</code> to ignore changes
360:             * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
361:             * @throws IllegalArgumentException  if <code>propertyName</code> is empty
362:             * @throws PropertyUnboundException  if <code>observeChanges</code>
363:             *     is true but the property is unbound, i. e. the <code>bean</code>
364:             *     does not provide a pair of methods to register a multicast
365:             *     PropertyChangeListener
366:             */
367:            public PropertyAdapter(B bean, String propertyName,
368:                    boolean observeChanges) {
369:                this (bean, propertyName, null, null, observeChanges);
370:            }
371:
372:            /**
373:             * Constructs a <code>PropertyAdapter</code> for the given bean,
374:             * property name, getter and setter name; does not observe changes.
375:             *
376:             * @param bean             the bean that owns the property
377:             * @param propertyName     the name of the adapted property
378:             * @param getterName       the optional name of the property reader
379:             * @param setterName       the optional name of the property writer
380:             * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
381:             * @throws IllegalArgumentException if <code>propertyName</code> is empty
382:             */
383:            public PropertyAdapter(B bean, String propertyName,
384:                    String getterName, String setterName) {
385:                this (bean, propertyName, getterName, setterName, false);
386:            }
387:
388:            /**
389:             * Constructs a <code>PropertyAdapter</code> for the given bean,
390:             * property name, getter and setter name; observes changes if specified.
391:             *
392:             * @param bean             the bean that owns the property
393:             * @param propertyName     the name of the adapted property
394:             * @param getterName       the optional name of the property reader
395:             * @param setterName       the optional name of the property writer
396:             * @param observeChanges   <code>true</code> to observe changes of bound
397:             *     or constrained properties, <code>false</code> to ignore changes
398:             * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
399:             * @throws IllegalArgumentException  if <code>propertyName</code> is empty
400:             * @throws PropertyUnboundException  if <code>observeChanges</code>
401:             *     is true but the property is unbound, i. e. the <code>bean</code>
402:             *     does not provide a pair of methods to register a multicast
403:             *     PropertyChangeListener
404:             */
405:            public PropertyAdapter(B bean, String propertyName,
406:                    String getterName, String setterName, boolean observeChanges) {
407:                this (new ValueHolder(bean, true), propertyName, getterName,
408:                        setterName, observeChanges);
409:            }
410:
411:            /**
412:             * Constructs a <code>PropertyAdapter</code> for the given
413:             * bean channel and property name; does not observe changes.
414:             *
415:             * @param beanChannel    the <code>ValueModel</code> that holds the bean
416:             * @param propertyName   the name of the adapted property
417:             * @throws NullPointerException if <code>beanChannel</code> or
418:             *     <code>propertyName</code> is <code>null</code>
419:             * @throws IllegalArgumentException if <code>propertyName</code> is empty
420:             */
421:            public PropertyAdapter(ValueModel beanChannel, String propertyName) {
422:                this (beanChannel, propertyName, false);
423:            }
424:
425:            /**
426:             * Constructs a <code>PropertyAdapter</code> for the given
427:             * bean channel and property name; observes changes if specified.
428:             *
429:             * @param beanChannel     the <code>ValueModel</code> that holds the bean
430:             * @param propertyName    the name of the adapted property
431:             * @param observeChanges  <code>true</code> to observe changes of bound
432:             *  or constrained properties, <code>false</code> to ignore changes
433:             * @throws NullPointerException if <code>beanChannel</code> or
434:             *     <code>propertyName</code> is <code>null</code>
435:             * @throws IllegalArgumentException if <code>propertyName</code> is empty
436:             * @throws PropertyUnboundException    if <code>observeChanges</code>
437:             *     is true but the property is unbound, i. e. the <code>bean</code>
438:             *     does not provide a pair of methods to register a multicast
439:             *     PropertyChangeListener
440:             * @throws PropertyAccessException if the <code>beanChannel</code>'s value
441:             *     does not provide a property descriptor for <code>propertyName</code>
442:             */
443:            public PropertyAdapter(ValueModel beanChannel, String propertyName,
444:                    boolean observeChanges) {
445:                this (beanChannel, propertyName, null, null, observeChanges);
446:            }
447:
448:            /**
449:             * Constructs a <code>PropertyAdapter</code> for the given bean channel,
450:             * property name, getter and setter name; does not observe changes.
451:             *
452:             * @param beanChannel    the <code>ValueModel</code> that holds the bean
453:             * @param propertyName   the name of the adapted property
454:             * @param getterName     the optional name of the property reader
455:             * @param setterName     the optional name of the property writer
456:             * @throws NullPointerException if <code>beanChannel</code> or
457:             *     <code>propertyName</code> is <code>null</code>
458:             * @throws IllegalArgumentException if <code>propertyName</code> is empty
459:             */
460:            public PropertyAdapter(ValueModel beanChannel, String propertyName,
461:                    String getterName, String setterName) {
462:                this (beanChannel, propertyName, getterName, setterName, false);
463:            }
464:
465:            /**
466:             * Constructs a <code>PropertyAdapter</code> for the given bean channel,
467:             * property name, getter and setter name; observes changes if specified.
468:             *
469:             * @param beanChannel     the <code>ValueModel</code> that holds the bean
470:             * @param propertyName    the name of the adapted property
471:             * @param getterName      the optional name of the property reader
472:             * @param setterName      the optional name of the property writer
473:             * @param observeChanges  <code>true</code> to observe changes of bound
474:             *  or constrained properties, <code>false</code> to ignore changes
475:             *
476:             * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
477:             * @throws IllegalArgumentException if <code>propertyName</code> is empty
478:             * @throws IllegalArgumentException if the bean channel is a ValueHolder
479:             *     that has the identityCheck feature disabled
480:             * @throws PropertyUnboundException    if <code>observeChanges</code>
481:             *     is true but the property is unbound, i. e. the <code>bean</code>
482:             *     does not provide a pair of methods to register a multicast
483:             *     PropertyChangeListener
484:             * @throws PropertyAccessException if the <code>beanChannel</code>'s value
485:             *     does not provide a property descriptor for <code>propertyName</code>
486:             */
487:            public PropertyAdapter(ValueModel beanChannel, String propertyName,
488:                    String getterName, String setterName, boolean observeChanges) {
489:                this .beanChannel = beanChannel != null ? beanChannel
490:                        : new ValueHolder(null, true);
491:                this .propertyName = propertyName;
492:                this .getterName = getterName;
493:                this .setterName = setterName;
494:                this .observeChanges = observeChanges;
495:
496:                if (propertyName == null)
497:                    throw new NullPointerException(
498:                            "The property name must not be null.");
499:                if (propertyName.length() == 0)
500:                    throw new IllegalArgumentException(
501:                            "The property name must not be empty.");
502:                checkBeanChannelIdentityCheck(beanChannel);
503:
504:                this .beanChannel
505:                        .addValueChangeListener(new BeanChangeHandler());
506:
507:                B initialBean = getBean();
508:                // Eagerly check the existence of the property to adapt.
509:                if (initialBean != null) {
510:                    getPropertyDescriptor(initialBean);
511:                    addChangeHandlerTo(initialBean);
512:                }
513:                storedOldBean = initialBean;
514:            }
515:
516:            // Accessors ************************************************************
517:
518:            /**
519:             * Returns the Java Bean that holds the adapted property.
520:             *
521:             * @return the Bean that holds the adapted property
522:             *
523:             * @see #setBean(Object)
524:             */
525:            public B getBean() {
526:                return (B) beanChannel.getValue();
527:            }
528:
529:            /**
530:             * Sets a new Java Bean as holder of the adapted property.
531:             * Notifies any registered value listeners if the value has changed.
532:             * Also notifies listeners that have been registered with this adapter
533:             * to observe the bound property <em>bean</em>.
534:             *
535:             * @param newBean  the new holder of the property
536:             *
537:             * @see #getBean()
538:             */
539:            public void setBean(B newBean) {
540:                beanChannel.setValue(newBean);
541:            }
542:
543:            /**
544:             * Returns the name of the adapted Java Bean property.
545:             *
546:             * @return the name of the adapted property
547:             */
548:            public String getPropertyName() {
549:                return propertyName;
550:            }
551:
552:            /**
553:             * Answers whether this adapter observes changes in the
554:             * adapted Bean property.
555:             *
556:             * @return true if this adapter observes changes, false if not
557:             */
558:            public boolean getObserveChanges() {
559:                return observeChanges;
560:            }
561:
562:            /*
563:             * Sets whether changes in the adapted Bean property shall be observed.
564:             * As a requirement the property must be bound.
565:             *
566:             * @param newValue  true to observe changes, false to ignore them
567:            public void setObserveChanges(boolean newValue) {
568:                if (newValue == getObserveChanges())
569:                    return;
570:                observeChanges = newValue;
571:                Object bean = getBean();
572:                removePropertyChangeHandler(bean);
573:                addPropertyChangeHandler(bean);
574:            }
575:             */
576:
577:            // ValueModel Implementation ********************************************
578:            /**
579:             * Returns the value of the bean's adapted property, <code>null</code>
580:             * if the current bean is <code>null</code>.<p>
581:             *
582:             * If the adapted bean property is write-only, this adapter is write-only
583:             * too, and this operation is not supported and throws an exception.
584:             *
585:             * @return the value of the adapted bean property, null if the bean is null
586:             * @throws UnsupportedOperationException  if the property is write-only
587:             * @throws PropertyNotFoundException      if the property could not be found
588:             * @throws PropertyAccessException        if the value could not be read
589:             */
590:            public Object getValue() {
591:                B bean = getBean();
592:                if (bean == null) {
593:                    return null;
594:                }
595:                return getValue0(bean);
596:            }
597:
598:            /**
599:             * Sets the given object as new value of the adapted bean property.
600:             * Does nothing if the bean is <code>null</code>. If the bean setter
601:             * throws a PropertyVetoException, it is silently ignored.
602:             * This write operation is supported only for writable bean properties.<p>
603:             *
604:             * Notifies any registered value listeners if the bean reports
605:             * a property change. Note that a bean may suppress PropertyChangeEvents
606:             * if the old and new value are the same, or if the old and new value
607:             * are equal.
608:             *
609:             * @param newValue   the value to set
610:             *
611:             * @throws UnsupportedOperationException if the property is read-only
612:             * @throws PropertyNotFoundException     if the property could not be found
613:             * @throws PropertyAccessException       if the new value could not be set
614:             */
615:            public void setValue(Object newValue) {
616:                B bean = getBean();
617:                if (bean == null)
618:                    return;
619:                try {
620:                    setValue0(bean, newValue);
621:                } catch (PropertyVetoException e) {
622:                    // Silently ignore this situation.
623:                }
624:            }
625:
626:            /**
627:             * Sets the given object as new value of the adapted bean property.
628:             * Does nothing if the bean is <code>null</code>. If the bean setter
629:             * throws a PropertyVetoExeption, this method throws the same exception.
630:             * This write operation is supported only for writable bean properties.<p>
631:             *
632:             * Notifies any registered value listeners if the bean reports
633:             * a property change. Note that a bean may suppress PropertyChangeEvents
634:             * if the old and new value are the same, or if the old and new value
635:             * are equal.
636:             *
637:             * @param newValue   the value to set
638:             *
639:             * @throws UnsupportedOperationException if the property is read-only
640:             * @throws PropertyNotFoundException     if the property could not be found
641:             * @throws PropertyAccessException       if the new value could not be set
642:             * @throws PropertyVetoException         if the invoked bean setter
643:             *     throws a PropertyVetoException
644:             *
645:             * @since 1.1
646:             */
647:            public void setVetoableValue(Object newValue)
648:                    throws PropertyVetoException {
649:                B bean = getBean();
650:                if (bean == null)
651:                    return;
652:                setValue0(getBean(), newValue);
653:            }
654:
655:            // Accessing the Changed State ********************************************
656:
657:            /**
658:             * Answers whether a bean property has changed since the changed state
659:             * has been reset. The changed state is implicitly reset every time
660:             * the target bean changes.
661:             *
662:             * @return true if a property of the current target bean
663:             *     has changed since the last reset
664:             */
665:            public boolean isChanged() {
666:                return changed;
667:            }
668:
669:            /**
670:             * Resets this tracker's changed state to <code>false</code>.
671:             */
672:            public void resetChanged() {
673:                setChanged(false);
674:            }
675:
676:            /**
677:             * Sets the changed state to the given value. Invoked by the global
678:             * PropertyChangeHandler that observes all bean changes. Also invoked
679:             * by <code>#resetChanged</code>.
680:             *
681:             * @param newValue  the new changed state
682:             */
683:            private void setChanged(boolean newValue) {
684:                boolean oldValue = isChanged();
685:                changed = newValue;
686:                firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue);
687:            }
688:
689:            // Releasing PropertyChangeListeners **************************************
690:
691:            /**
692:             * Removes the PropertyChangeHandler from the observed bean, if the bean
693:             * is not <code>null</code> and if property changes are observed.<p>
694:             *
695:             * PropertyAdapters that observe changes have a PropertyChangeListener
696:             * registered with the target bean. Hence, a bean has a reference to all
697:             * PropertyAdapters that observe it. To avoid memory leaks it is recommended
698:             * to remove this listener if the bean lives much longer than the
699:             * PropertyAdapter, enabling the garbage collector to remove the adapter.
700:             * To do so, you can call <code>setBean(null)</code> or set the
701:             * bean channel's value to null.
702:             * As an alternative you can use event listener lists in your beans
703:             * that implement references with <code>WeakReference</code>.<p>
704:             *
705:             * Setting the bean to null has side-effects, for example
706:             * this adapter fires a change event for the bound property <em>bean</em>
707:             * and other properties. And this adpter's value may change.
708:             * However, typically this is fine and setting the bean to null is
709:             * the first choice for removing the reference from the bean to the adapter.
710:             * Another way to clear the reference from the target bean is
711:             * to call <code>#release</code>. It has no side-effects, but the adapter
712:             * must not be used anymore once #release has been called.
713:             *
714:             * @see #setBean(Object)
715:             * @see java.lang.ref.WeakReference
716:             */
717:            public void release() {
718:                removeChangeHandlerFrom(getBean());
719:            }
720:
721:            // Changing the Bean & Adding and Removing the PropertyChangeHandler ******
722:
723:            private void setBean0(B oldBean, B newBean) {
724:                firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean,
725:                        true);
726:                removeChangeHandlerFrom(oldBean);
727:                forwardAdaptedValueChanged(oldBean, newBean);
728:                resetChanged();
729:                addChangeHandlerTo(newBean);
730:                firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean, true);
731:                firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean,
732:                        true);
733:            }
734:
735:            private void forwardAdaptedValueChanged(B oldBean, B newBean) {
736:                Object oldValue = (oldBean == null)
737:                        || isWriteOnlyProperty(oldBean) ? null
738:                        : getValue0(oldBean);
739:                Object newValue = (newBean == null)
740:                        || isWriteOnlyProperty(newBean) ? null
741:                        : getValue0(newBean);
742:                if (oldValue != null || newValue != null) {
743:                    fireValueChange(oldValue, newValue, true);
744:                }
745:            }
746:
747:            private void forwardAdaptedValueChanged(B newBean) {
748:                Object newValue = (newBean == null)
749:                        || isWriteOnlyProperty(newBean) ? null
750:                        : getValue0(newBean);
751:                fireValueChange(null, newValue);
752:            }
753:
754:            /**
755:             * Adds a property change listener to the given bean if we observe changes
756:             * and the bean is not null. First checks whether the bean class
757:             * supports <em>bound properties</em>, i.e. it provides a pair of methods
758:             * to register multicast property change event listeners;
759:             * see section 7.4.1 of the Java Beans specification for details.
760:             *
761:             * @param bean  the bean to add a property change handler.
762:             * @throws PropertyUnboundException
763:             *     if the bean does not support bound properties
764:             * @throws PropertyNotBindableException
765:             *     if the property change handler cannot be added successfully
766:             *
767:             * @see #removeChangeHandlerFrom(Object)
768:             */
769:            private void addChangeHandlerTo(B bean) {
770:                if (!observeChanges || bean == null)
771:                    return;
772:
773:                propertyChangeHandler = new PropertyChangeHandler();
774:                BeanUtils.addPropertyChangeListener(bean, getBeanClass(bean),
775:                        propertyChangeHandler);
776:            }
777:
778:            /**
779:             * Removes the formerly added property change handler from the given bean
780:             * if we observe changes and the bean is not null.
781:             *
782:             * @param bean  the bean to remove the property change handler from.
783:             * @throws PropertyUnboundException
784:             *     if the bean does not support bound properties
785:             * @throws PropertyNotBindableException
786:             *     if the property change handler cannot be removed successfully
787:             *
788:             * @see #addChangeHandlerTo(Object)
789:             */
790:            private void removeChangeHandlerFrom(B bean) {
791:                if (!observeChanges || bean == null)
792:                    return;
793:
794:                BeanUtils.removePropertyChangeListener(bean,
795:                        getBeanClass(bean), propertyChangeHandler);
796:                propertyChangeHandler = null;
797:            }
798:
799:            // Helper Methods to Get and Set a Property Value *************************
800:
801:            /**
802:             * Returns the Java Bean class used by this adapter.
803:             * The current implementation just returns the given bean's class.<p>
804:             *
805:             * A future version may return a type other than the concrete
806:             * class of the given bean. This beanClass could be specified
807:             * in a new set of constructors. This is useful if the beans
808:             * are specified by public interfaces, and implemented by
809:             * package private classes. In this case, the class of the given bean
810:             * object shall be checked against the specified type.
811:             *
812:             * @param bean    the bean that may be used to lookup the class from
813:             * @return the Java Bean class used for this adapter.
814:             */
815:            private Class<?> getBeanClass(B bean) {
816:                return bean.getClass();
817:                // The future version shall add a check like
818:                // beanClass.isInstance(bean) if the beanClass
819:                // has been specified in the constructor.
820:            }
821:
822:            /**
823:             * Returns the current value of the bean's property, <code>null</code>
824:             * if the current bean is <code>null</code>.
825:             *
826:             * @param bean   the bean to read the value from
827:             * @return the bean's property value
828:             */
829:            private Object getValue0(B bean) {
830:                return bean == null ? null : BeanUtils.getValue(bean,
831:                        getPropertyDescriptor(bean));
832:            }
833:
834:            /**
835:             * Sets the given object as new value of the adapted bean property.
836:             * Does nothing if the bean is <code>null</code>. This operation
837:             * is unsupported if the bean property is read-only.<p>
838:             *
839:             * The operation is delegated to the <code>BeanUtils</code> class.
840:             *
841:             * @param bean      the bean that holds the adapted property
842:             * @param newValue  the property value to be set
843:             *
844:             * @throws NullPointerException   if the bean is null
845:             * @throws PropertyVetoException  if the invoked bean setter
846:             *     throws a PropertyVetoException
847:             */
848:            private void setValue0(B bean, Object newValue)
849:                    throws PropertyVetoException {
850:                BeanUtils.setValue(bean, getPropertyDescriptor(bean), newValue);
851:            }
852:
853:            /**
854:             * Looks up, lazily initializes and returns a <code>PropertyDescriptor</code>
855:             * for the given Java Bean and name of the adapted property.<p>
856:             *
857:             * The cached PropertyDescriptor is considered invalid if the
858:             * bean's class has changed. In this case we recompute the
859:             * PropertyDescriptor.<p>
860:             *
861:             * If a getter name or setter name is available, these are used
862:             * to directly create a PropertyDescriptor. Otherwise, the standard
863:             * Java Bean introspection is used to determine the property descriptor.
864:             *
865:             * @param bean          the bean that holds the property
866:             * @return the <code>PropertyDescriptor</code>
867:             * @throws PropertyNotFoundException   if the property could not be found
868:             */
869:            private PropertyDescriptor getPropertyDescriptor(B bean) {
870:                Class<?> beanClass = getBeanClass(bean);
871:                if ((cachedPropertyDescriptor == null)
872:                        || (beanClass != cachedBeanClass)) {
873:
874:                    cachedPropertyDescriptor = BeanUtils.getPropertyDescriptor(
875:                            beanClass, getPropertyName(), getterName,
876:                            setterName);
877:                    cachedBeanClass = beanClass;
878:                }
879:                return cachedPropertyDescriptor;
880:            }
881:
882:            /**
883:             * Answers whether the adapted property has a setter but no getter.
884:             * In this case the PropertyAdapter doesn't check for the old value
885:             * when you set a new bean or a new value.
886:             *
887:             * @param bean   the bean to test for the write only state
888:             * @return true if the property has a setter but no getter, false otherwise
889:             */
890:            private boolean isWriteOnlyProperty(B bean) {
891:                return null == getPropertyDescriptor(bean).getReadMethod();
892:            }
893:
894:            /**
895:             * Throws an IllegalArgumentException if the given ValueModel
896:             * is a ValueHolder that has the identityCheck feature disabled.
897:             */
898:            private void checkBeanChannelIdentityCheck(ValueModel valueModel) {
899:                if (!(valueModel instanceof  ValueHolder))
900:                    return;
901:
902:                ValueHolder valueHolder = (ValueHolder) valueModel;
903:                if (!valueHolder.isIdentityCheckEnabled())
904:                    throw new IllegalArgumentException(
905:                            "The bean channel must have the identity check enabled.");
906:            }
907:
908:            // Helper Classes *********************************************************
909:
910:            /**
911:             * Listens to changes of the bean.
912:             */
913:            private final class BeanChangeHandler implements 
914:                    PropertyChangeListener {
915:
916:                /**
917:                 * The bean has been changed. Uses the stored old bean instead of
918:                 * the event's old value, because the latter can be null.
919:                 * If the event's new value is null, the new bean is requested
920:                 * from the bean channel.
921:                 *
922:                 * @param evt   the property change event to be handled
923:                 */
924:                public void propertyChange(PropertyChangeEvent evt) {
925:                    B newBean = evt.getNewValue() != null ? (B) evt
926:                            .getNewValue() : getBean();
927:                    setBean0(storedOldBean, newBean);
928:                    storedOldBean = newBean;
929:                }
930:            }
931:
932:            /**
933:             * Listens to changes of all bean properties. Fires property changes
934:             * if the associated property or an arbitrary set of properties has changed.
935:             */
936:            private final class PropertyChangeHandler implements 
937:                    PropertyChangeListener {
938:
939:                /**
940:                 * A bean property has been changed. Sets the changed state to true.
941:                 * Checks whether the observed
942:                 * property or multiple properties have changed.
943:                 * If so, notifies all registered listeners about the change.
944:                 *
945:                 * @param evt   the property change event to be handled
946:                 */
947:                public void propertyChange(PropertyChangeEvent evt) {
948:                    setChanged(true);
949:                    if (evt.getPropertyName() == null) {
950:                        forwardAdaptedValueChanged(getBean());
951:                    } else if (evt.getPropertyName().equals(getPropertyName())) {
952:                        fireValueChange(evt.getOldValue(), evt.getNewValue(),
953:                                true);
954:                    }
955:                }
956:            }
957:
958:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.