001: /*
002: *
003: * JMoney - A Personal Finance Manager
004: * Copyright (c) 2004 Nigel Westbury <westbury@users.sourceforge.net>
005: *
006: *
007: * This program is free software; you can redistribute it and/or modify
008: * it under the terms of the GNU General Public License as published by
009: * the Free Software Foundation; either version 2 of the License, or
010: * (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: *
021: */
022:
023: package net.sf.jmoney.model2;
024:
025: import java.lang.ref.WeakReference;
026: import java.util.Collection;
027: import java.util.Vector;
028:
029: import org.eclipse.core.runtime.IAdaptable;
030: import org.eclipse.swt.events.DisposeEvent;
031: import org.eclipse.swt.events.DisposeListener;
032: import org.eclipse.swt.widgets.Control;
033:
034: /**
035: * An interface to an object that manages a view on the data.
036: * This is a base interface that is extended by ISessionManager to
037: * manage a view of data committed to a datastore and is also
038: * extended by ITransactionManager to manage a view of uncommitted data.
039: */
040: public abstract class DataManager implements IAdaptable {
041:
042: private Vector<WeakReference<SessionChangeListener>> sessionChangeListenerRefs = new Vector<WeakReference<SessionChangeListener>>();
043:
044: private Vector<SessionChangeListener> sessionChangeListeners = new Vector<SessionChangeListener>();
045:
046: private Vector<SessionChangeFirerListener> sessionChangeFirerListeners = new Vector<SessionChangeFirerListener>();
047:
048: private boolean sessionFiring = false;
049:
050: // private ReferenceQueue referenceQueue = new ReferenceQueue();
051:
052: public void addChangeListener(SessionChangeListener listener) {
053: sessionChangeListeners.add(listener);
054: }
055:
056: public void removeChangeListener(SessionChangeListener listener) {
057: sessionChangeListeners.remove(listener);
058: }
059:
060: /**
061: * Adds a change listener.
062: * <P>
063: * Adds the listener to the collection of listeners who will be notified
064: * when a change is made to the version of the datastore as seen through
065: * this data manager. Notifications will be sent when either a
066: * change is committed to this data view by a transaction manager
067: * or an uncommitted change is made through this data manager.
068: * <P>
069: * When listening for changes to a datastore, there are two options.
070: * If the listener is interested only in receiving committed changes
071: * then the listener should listen to the Session object or the JMoneyPlugin
072: * object. However, if a listener wants to be notified of changes
073: * made through a transaction manager, even though those changes are
074: * not committed to the datastore, then the listener should add the
075: * listener to the transaction manager using this method.
076: * <P>
077: * The listener will not recieve any notification at the time a transaction
078: * is committed as the listener will already have been notified of the
079: * changes. Note that there is no support for rollbacks as the transaction
080: * manager can be just dropped (and garbage collected) without ever having been
081: * committed, so getting a notification for a change that is never committed is
082: * not an issue. Views should do a full refresh if they change the data manager
083: * through which they are obtaining the data to be shown.
084: * <P>
085: * This method maintains only a weak reference to the listener. Therefore
086: * the caller MUST maintain a reference to the listener. If the caller does
087: * not maintain a reference to the listener then the listener will be garbage
088: * collected and the caller may wonder why no events are being notified.
089: */
090: public void addChangeListenerWeakly(SessionChangeListener listener) {
091: sessionChangeListenerRefs
092: .add(new WeakReference<SessionChangeListener>(listener));
093: }
094:
095: /**
096: * Adds a change listener.
097: * <P>
098: * The listener is active only for as long as the given control exists. When the
099: * given control is disposed, the listener is removed and will receive no more
100: * notifications.
101: * <P>
102: * This method is generally used when a listener is used to update contents in a
103: * control. Typically multiple controls are updated by a listener and the parent
104: * composite control is passed to this method.
105: * <P>
106: * This method creates a strong reference to the listener. There is thus no need
107: * for the caller to maintain a reference to the listener.
108: *
109: * @param listener
110: * @param control
111: */
112: public void addChangeListener(final SessionChangeListener listener,
113: Control control) {
114: sessionChangeListeners.add(listener);
115:
116: // Remove the listener when the given control is disposed.
117: control.addDisposeListener(new DisposeListener() {
118: public void widgetDisposed(DisposeEvent e) {
119: sessionChangeListeners.remove(listener);
120: }
121: });
122: }
123:
124: public void addSessionChangeFirerListener(
125: SessionChangeFirerListener listener) {
126: sessionChangeFirerListeners.add(listener);
127: }
128:
129: public void removeSessionChangeFirerListener(
130: SessionChangeFirerListener listener) {
131: sessionChangeFirerListeners.remove(listener);
132: }
133:
134: /**
135: * Send change notifications to all listeners who are listening for changes
136: * to the version of the datastore as seen through this data manager.
137: * <P>
138: * In practice it is likely that the only listener will be the JMoneyPlugin
139: * object. Views should all listen to the JMoneyPlugin class for changes to
140: * the model. The JMoneyPlugin object will pass on events from this session
141: * object.
142: * <P>
143: * Listeners may register directly with a session object. However if they do
144: * so then they must re-register whenever the session object changes. If a
145: * viewer wants to listen for changes to a session even if that session is
146: * not the session currently shown in the workbench then it should register
147: * with the session object, but if the viewer wants to be told about changes
148: * to the current workbench window then it should register with the
149: * JMoneyPlugin object.
150: *
151: * This method is public because the layer above this data manager is
152: * responsible for calling this method. Only the above layer (code outside
153: * the data manager) knows, for example, when objectInserted should be
154: * called.
155: */
156: public void fireEvent(ISessionChangeFirer firer) {
157: sessionFiring = true;
158:
159: /*
160: * Notify listeners who are listening to us using the
161: * SessionChangeFirerListener interface.
162: */
163: if (!sessionChangeFirerListeners.isEmpty()) {
164: /*
165: * Take a copy of the listener list. By doing this we allow
166: * listeners to safely add or remove listeners.
167: */
168: SessionChangeFirerListener listenerArray[] = new SessionChangeFirerListener[sessionChangeFirerListeners
169: .size()];
170: sessionChangeFirerListeners.copyInto(listenerArray);
171: for (int i = 0; i < listenerArray.length; i++) {
172: listenerArray[i].sessionChanged(firer);
173: }
174: }
175:
176: /*
177: * Notify listeners who are listening to us using the
178: * SessionChangeListener interface.
179: */
180:
181: /*
182: * Take a copy of the listener list. By doing this we allow
183: * listeners to safely add or remove listeners.
184: */
185: Vector<SessionChangeListener> listenerArray = new Vector<SessionChangeListener>();
186:
187: for (WeakReference<SessionChangeListener> listenerRef : sessionChangeListenerRefs) {
188: SessionChangeListener listener = listenerRef.get();
189: if (listener != null) {
190: listenerArray.add(listener);
191: }
192: }
193:
194: for (SessionChangeListener listener : sessionChangeListeners) {
195: listenerArray.add(listener);
196: }
197:
198: for (SessionChangeListener listener : listenerArray) {
199: firer.fire(listener);
200: }
201:
202: sessionFiring = false;
203: }
204:
205: /**
206: * This method is used by plug-ins so that they know if
207: * code is being called from within change notification.
208: *
209: * It is important for plug-ins to know this. Plug-ins
210: * MUST NOT change the session data while a listener is
211: * being notified of a change to the datastore.
212: * This can happen very indirectly. For example, suppose
213: * an account is deleted. The navigation view's listener
214: * is notified and so removes the account's node from the
215: * navigation tree. If an account properties panel is
216: * open, the panel is destroyed. Because the panel is
217: * being destroyed, the control that had the focus is sent
218: * a 'focus lost' notification. The 'focus lost' notification
219: * takes the edited data from the control and writes it to
220: * the datastore.
221: * <P>
222: * Writing data to the datastore during session change notifications
223: * can cause serious problems. The data may conflict. The
224: * undo/redo operations are almost impossible to manage.
225: * In the above scenario with the deleted account, an attempt
226: * is made to update a property for an object that has been
227: * deleted. The problems are endless.
228: * <P>
229: * It would be good if the datastore simply ignored such changes.
230: * This would provide more robust support for plug-ins, and plug-ins
231: * would not have to test this flag. However, for the time being,
232: * plug-ins must test this flag and avoid making changes when this
233: * flag is set. Plug-ins only need to do this in focus lost events
234: * as that is the only time I can think of where this problem may
235: * occur.
236: *
237: * @return true if the session is notifying listeners of
238: * a change to the session data, otherwise false
239: */
240: // TODO: Revisit this, especially the last paragraph above.
241: public boolean isSessionFiring() {
242: return sessionFiring;
243: }
244:
245: /**
246: * This method is called when a transaction is about to start.
247: * <P>
248: * If the datastore is kept in a transactional database then the code
249: * needed to start a transaction should be put in the implementation
250: * of this method.
251: * <P>
252: * The framework will always call this method, then make changes to
253: * the datastore, then call <code>commitTransaction</code> within
254: * a single function call. The framework also ensures that no events
255: * are fired between the call to <code>startTransaction</code> and
256: * the call to <code>commitTransaction</code>. The implementation of
257: * this method thus has no need to support or guard against nested
258: * transactions.
259: *
260: * @see commitTransaction
261: */
262: public abstract void startTransaction();
263:
264: /**
265: * This method is called when a transaction is to be committed.
266: * <P>
267: * If the datastore is kept in a transactional database then the code
268: * needed to commit the transaction should be put in the implementation
269: * of this method.
270: *
271: * @see startTransaction
272: */
273: public abstract void commitTransaction();
274:
275: /** Returns the session object. The session object must be
276: * non-null.
277: *
278: * @return the session object
279: */
280: public abstract Session getSession();
281:
282: /**
283: * @param account
284: * @return
285: */
286: public abstract boolean hasEntries(Account account);
287:
288: /**
289: * @param account
290: * @return
291: */
292: public abstract Collection<Entry> getEntries(Account account);
293: }
294:
295: /*
296: //@author Santhosh Kumar T - santhosh@in.fiorano.com
297: public class WeakPropertyChangeListener implements PropertyChangeListener{
298: WeakReference listenerRef;
299: Object src;
300:
301: public WeakPropertyChangeListener(PropertyChangeListener listener, Object src){
302: listenerRef = new WeakReference(listener);
303: this.src = src;
304: }
305:
306: public void propertyChange(PropertyChangeEvent evt){
307: PropertyChangeListener listener = (PropertyChangeListener)listenerRef.get();
308: if(listener==null){
309: removeListener();
310: }else
311: listener.propertyChange(evt);
312: }
313:
314: private void removeListener(){
315: try{
316: Method method = src.getClass().getMethod("removePropertyChangeListener"
317: , new Class[] {PropertyChangeListener.class});
318: method.invoke(src, new Object[]{ this });
319: } catch(Exception e){
320: e.printStackTrace();
321: }
322: }
323: }
324:
325:
326: How to use this:
327:
328: KeyboardFocusManager focusManager =
329: KeyboardFocusManager.getCurrentKeyboardFocusManager();
330:
331: // instead of registering direclty use weak listener
332: // focusManager.addPropertyChangeListener(focusOwnerListener);
333:
334: focusManager.addPropertyChangeListener(
335: new WeakPropertyChangeListener(focusOwnerListener, focusManager));
336:
337:
338: How does this work:
339:
340: Instead of registering propertyChangeListener directly to keyboardFocusManager, we wrap it inside WeakPropertyChangeListener and register this weak listener to keyboardFocusManager. This weak listener acts a delegate.
341: It receives the propertyChangeEvents from keyboardFocusManager and delegates it the wrapped listener.
342:
343: The interesting part of this weak listener, it hold a weakReference to the original propertyChangeListener. so this delegate is eligible for garbage collection which it is no longer reachable via references. When it gets garbage
344: collection, the weakReference will be pointing to null. On next propertyChangeEvent notification from keyboardFocusManager, it find that the weakReference is pointing to null, and unregisters itself from
345: keyboardFocusManager. Thus the weak listener will also become eligible for garbage collection in next gc cycle.
346:
347: This concept is not something new. If you have a habit of looking into swing sources, you will find that AbstractButton actually adds a weak listener to its action. The weak listener class used for this is :
348: javax.swing.AbstractActionPropertyChangeListener; This class is package-private, so you don't find it in javadoc.
349:
350: The full-fledged, generic implementation of weak listeners is available in Netbeans OpenAPI: WeakListeners.java . It is worth to have a look at it.
351:
352: We create a subclass of ReferenceQueue, which will help us in performing such resource cleanup very easily:
353:
354: // @author Santhosh Kumar T - santhosh@in.fiorano.com
355: public class ActiveReferenceQueue
356: extends ReferenceQueue implements Runnable{
357: private static ActiveReferenceQueue singleton = null;
358:
359: public static ActiveReferenceQueue getInstance(){
360: if(singleton==null)
361: singleton = new ActiveReferenceQueue();
362: return singleton;
363: }
364:
365: private ActiveReferenceQueue(){
366: Thread t = new Thread(this, "ActiveReferenceQueue");
367: t.setDaemon(false);
368: t.start();
369: }
370:
371: public void run(){
372: for(;;){
373: try{
374: Reference ref = super.remove(0);
375: if(ref instanceof Runnable){
376: try{
377: ((Runnable)ref).run();
378: } catch(Exception e){
379: e.printStackTrace();
380: }
381: }
382: } catch(InterruptedException e){
383: e.printStackTrace();
384: }
385: }
386: }
387: }
388:
389:
390: ActiveReferenceQueue is a singleton class, and starts a thread when it is instantiated. This thread keeps polling for weakReferences that are eligible for garbage collection, and if those weakReferenes implement Runnable interface, it executes them.
391:
392: To use this, we can create a subclass of WeakReference implementing Runnable interface, which does the cleanup it run() method.
393:
394: Let us see how we will change the WeakPropertyChangeListener to use the above reference queue:
395:
396: // @author Santhosh Kumar T - santhosh@in.fiorano.com
397: public class WeakPropertyChangeListener implements PropertyChangeListener{
398: WeakReference listenerRef;
399: Object src;
400:
401: public WeakPropertyChangeListener(PropertyChangeListener listener, Object src){
402: listenerRef = new ListenerReference(listener, this);
403: this.src = src;
404: }
405:
406: public void propertyChange(PropertyChangeEvent evt){
407: PropertyChangeListener listener = (PropertyChangeListener)listenerRef.get();
408: if(listener!=null)
409: listener.propertyChange(evt);
410: }
411:
412: private void removeListener(){
413: try{
414: Method method = src.getClass().getMethod("removePropertyChangeListener"
415: , new Class[] {PropertyChangeListener.class});
416: method.invoke(src, new Object[]{ this });
417: } catch(Exception e){
418: e.printStackTrace();
419: }
420: }
421:
422: static class ListenerReference extends WeakReference{
423: WeakPropertyChangeListener listener;
424:
425: public ListenerReference(Object ref, WeakPropertyChangeListener listener){
426: super(ref, ActiveReferenceQueue.getInstance());
427: this.listener = listener;
428: }
429:
430: public void run(){
431: listener.removeListener();
432: listener = null;
433: }
434: }
435: }
436: */
|