001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022
023package org.biojava.utils;
024
025import java.lang.ref.Reference;
026import java.lang.ref.ReferenceQueue;
027import java.lang.ref.WeakReference;
028import java.util.ArrayList;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033
034/**
035 * implements Changeable support with a ChangeHub that
036 * stores ChangeListener by key.
037 *
038 * @author Thomas Down (original implementation)
039 * @author David Huen (refactoring)
040 * @since 1.3
041 */
042public abstract class IndexedChangeHub implements ChangeHub
043{
044    private ReferenceQueue refQueue;
045    private Map listeners;
046
047    public IndexedChangeHub()
048    {
049        refQueue = new ReferenceQueue();
050        listeners = new HashMap();
051    }
052
053    // queue cleanup
054    // the references are now safe
055
056    abstract protected boolean isMyChangeEvent(ChangeEvent cev, IndexedChangeHub.ListenerMemento lm);
057
058    public void addListener(Object key, ChangeListener listener, ChangeType ct)
059    {
060        diddleQueue();
061        List listenerList = (List) listeners.get(key);
062        if (listenerList == null) {
063            listenerList = new ArrayList();
064            listeners.put(key, listenerList);
065        }
066        listenerList.add(new ListenerMemento(ct, new ListenerReference(key, listener, refQueue)));
067    }
068
069    public void removeListener(Object key, ChangeListener listener, ChangeType ct)
070    {
071        List listenerList = (List) listeners.get(key);
072        if (listenerList != null) {
073            for (Iterator i = listenerList.iterator(); i.hasNext(); ) {
074                ListenerMemento lm = (ListenerMemento) i.next();
075                if (ct == lm.type && listener.equals(lm.listener.get())) {
076                    lm.listener.clear();
077                    i.remove();
078                    return;
079                }
080            }
081        }
082    }
083
084    public void firePreChange(Object key, ChangeEvent cev)
085        throws ChangeVetoException
086    {
087        List listenerList = (List) listeners.get(key);
088        if (listenerList != null) {
089            for (Iterator i = listenerList.iterator(); i.hasNext(); ) {
090                ListenerMemento lm = (ListenerMemento) i.next();
091                if (isMyChangeEvent(cev, lm)) {
092                    ChangeListener cl = (ChangeListener) lm.listener.get();
093                    if (cl != null) {
094                        cl.preChange(cev);
095                    }
096                }
097            }
098        }
099
100        // in the original version, there was the possibility of firing a
101        // ChangeEvent for the parent class here.  This is not feasible
102        // in this implementation.  The child must override this method
103        // and fire it themselves.
104
105    }
106
107    public void firePostChange(Object key, ChangeEvent cev)
108    {
109        List listenerList = (List) listeners.get(key);
110        if (listenerList != null) {
111            for (Iterator i = listenerList.iterator(); i.hasNext(); ) {
112                ListenerMemento lm = (ListenerMemento) i.next();
113                if (isMyChangeEvent(cev, lm)) {
114                    ChangeListener cl = (ChangeListener) lm.listener.get();
115                    if (cl != null) {
116                        cl.postChange(cev);
117                    }
118                }
119            }
120        }
121        // in the original version, there was the possibility of firing a
122        // ChangeEvent for the parent class here.  This is not feasible
123        // in this implementation.  The child must override this method
124        // and fire it themselves.
125
126    }
127
128    protected void diddleQueue()
129    {
130        Reference ref;
131        while ((ref = refQueue.poll()) != null) {
132            List listenerList = (List) listeners.get(((ListenerReference) ref).getKey());
133            if (listenerList != null) {
134                for (Iterator i = listenerList.iterator(); i.hasNext(); ) {
135                    ListenerMemento lm = (ListenerMemento) i.next();
136                    if (lm.listener == ref) {
137                        i.remove();
138                        break;
139                    }
140                }
141            }
142        }
143    }
144
145    private class ListenerReference extends WeakReference {
146        private Object key;
147
148        public ListenerReference(Object key, Object ref) {
149            super(ref);
150            this.key = key;
151        }
152
153        public ListenerReference(Object key, Object ref, ReferenceQueue queue) {
154            super(ref, queue);
155            this.key = key;
156        }
157
158        public Object getKey() {
159            return key;
160        }
161    }
162
163    protected class ListenerMemento {
164        public final ChangeType type;
165        public final Reference listener;
166
167        public ListenerMemento(ChangeType type, Reference listener) {
168            this.type = type;
169            this.listener = listener;
170        }
171    }
172
173}
174