001    /*
002     * Copyright 2002-2004 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.tree;
017    
018    import java.beans.PropertyChangeEvent;
019    import java.beans.PropertyChangeListener;
020    
021    import javax.swing.event.EventListenerList;
022    import javax.swing.event.TreeModelEvent;
023    import javax.swing.event.TreeModelListener;
024    import javax.swing.tree.TreeModel;
025    
026    import org.springframework.binding.value.ValueModel;
027    
028    /**
029     * 
030     * @author Keith Donald
031     */
032    public abstract class AbstractTreeModel implements TreeModel {
033    
034        private final RootHolderChangeHandler rootHolderChangeHandler = new RootHolderChangeHandler();
035    
036        private final EventListenerList listenerList = new EventListenerList();
037    
038        private Object root;
039    
040        private ValueModel rootHolder;
041    
042        protected AbstractTreeModel(Object root) {
043            this.root = root;
044        }
045    
046        protected AbstractTreeModel(ValueModel rootHolder) {
047            this.rootHolder = rootHolder;
048            this.root = rootHolder.getValue();
049            this.rootHolder.addValueChangeListener(rootHolderChangeHandler);
050        }
051    
052        /*
053         * @see javax.swing.tree.TreeModel#getRoot()
054         */
055        public Object getRoot() {
056            return root;
057        }
058    
059        protected ValueModel getRootHolder() {
060            return rootHolder;
061        }
062    
063        /**
064         * Adds a listener for the TreeModelEvent posted after the tree changes.
065         * 
066         * @see #removeTreeModelListener
067         * @param l
068         *            the listener to add
069         */
070        public void addTreeModelListener(TreeModelListener l) {
071            listenerList.add(TreeModelListener.class, l);
072        }
073    
074        /**
075         * Removes a listener previously added with <B>addTreeModelListener() </B>.
076         * 
077         * @see #addTreeModelListener
078         * @param l
079         *            the listener to remove
080         */
081        public void removeTreeModelListener(TreeModelListener l) {
082            listenerList.remove(TreeModelListener.class, l);
083        }
084    
085        /**
086         * Returns an array of all the tree model listeners registered on this
087         * model.
088         * 
089         * @return all of this model's <code>TreeModelListener</code> s or an
090         *         empty array if no tree model listeners are currently registered
091         * 
092         * @see #addTreeModelListener
093         * @see #removeTreeModelListener
094         */
095        public TreeModelListener[] getTreeModelListeners() {
096            return (TreeModelListener[]) listenerList.getListeners(TreeModelListener.class);
097        }
098    
099        /**
100         * Notifies all listeners that have registered interest for notification on
101         * this event type. The event instance is lazily created using the
102         * parameters passed into the fire method.
103         * 
104         * @param source
105         *            the node being changed
106         * @param path
107         *            the path to the root node
108         * @param childIndices
109         *            the indices of the changed elements
110         * @param children
111         *            the changed elements
112         * @see EventListenerList
113         */
114        protected void fireTreeNodesChanged(Object[] path, int[] childIndices, Object[] children) {
115            // Guaranteed to return a non-null array
116            Object[] listeners = listenerList.getListenerList();
117            TreeModelEvent e = null;
118            // Process the listeners last to first, notifying
119            // those that are interested in this event
120            for (int i = listeners.length - 2; i >= 0; i -= 2) {
121                if (listeners[i] == TreeModelListener.class) {
122                    // Lazily create the event:
123                    if (e == null) {
124                        e = new TreeModelEvent(this, path, childIndices, children);
125                    }
126                    ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
127                }
128            }
129        }
130    
131        /**
132         * Notifies all listeners that have registered interest for notification on
133         * this event type. The event instance is lazily created using the
134         * parameters passed into the fire method.
135         * 
136         * @param source
137         *            the node where new elements are being inserted
138         * @param path
139         *            the path to the root node
140         * @param childIndices
141         *            the indices of the new elements
142         * @param children
143         *            the new elements
144         * @see EventListenerList
145         */
146        protected void fireTreeNodesInserted(Object[] path, int[] childIndices, Object[] children) {
147            // Guaranteed to return a non-null array
148            Object[] listeners = listenerList.getListenerList();
149            TreeModelEvent e = null;
150            // Process the listeners last to first, notifying
151            // those that are interested in this event
152            for (int i = listeners.length - 2; i >= 0; i -= 2) {
153                if (listeners[i] == TreeModelListener.class) {
154                    // Lazily create the event:
155                    if (e == null) {
156                        e = new TreeModelEvent(this, path, childIndices, children);
157                    }
158                    ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
159                }
160            }
161        }
162    
163        /**
164         * Notifies all listeners that have registered interest for notification on
165         * this event type. The event instance is lazily created using the
166         * parameters passed into the fire method.
167         * 
168         * @param source
169         *            the node where elements are being removed
170         * @param path
171         *            the path to the root node
172         * @param childIndices
173         *            the indices of the removed elements
174         * @param children
175         *            the removed elements
176         * @see EventListenerList
177         */
178        protected void fireTreeNodesRemoved(Object[] path, int[] childIndices, Object[] children) {
179            // Guaranteed to return a non-null array
180            Object[] listeners = listenerList.getListenerList();
181            TreeModelEvent e = null;
182            // Process the listeners last to first, notifying
183            // those that are interested in this event
184            for (int i = listeners.length - 2; i >= 0; i -= 2) {
185                if (listeners[i] == TreeModelListener.class) {
186                    // Lazily create the event:
187                    if (e == null) {
188                        e = new TreeModelEvent(this, path, childIndices, children);
189                    }
190                    ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
191                }
192            }
193        }
194    
195        /**
196         * Notifies all listeners that have registered interest for notification on
197         * this event type. The event instance is lazily created using the
198         * parameters passed into the fire method.
199         * 
200         * @param source
201         *            the node where the tree model has changed
202         * @param path
203         *            the path to the root node
204         * @param childIndices
205         *            the indices of the affected elements
206         * @param children
207         *            the affected elements
208         * @see EventListenerList
209         */
210        protected void fireTreeStructureChanged(Object[] path, int[] childIndices, Object[] children) {
211            // Guaranteed to return a non-null array
212            Object[] listeners = listenerList.getListenerList();
213            TreeModelEvent e = null;
214            // Process the listeners last to first, notifying
215            // those that are interested in this event
216            for (int i = listeners.length - 2; i >= 0; i -= 2) {
217                if (listeners[i] == TreeModelListener.class) {
218                    // Lazily create the event:
219                    if (e == null) {
220                        e = new TreeModelEvent(this, path, childIndices, children);
221                    }
222                    ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
223                }
224            }
225        }
226    
227        /**
228         * The only event raised by this model is TreeStructureChanged with the root
229         * as path, i.e. the whole tree has changed.
230         */
231        protected void fireRootNodeChanged(Object previousRoot) {
232            // Guaranteed to return a non-null array
233            Object[] listeners = listenerList.getListenerList();
234            TreeModelEvent e = null;
235            // Process the listeners last to first, notifying
236            // those that are interested in this event
237            for (int i = listeners.length - 2; i >= 0; i -= 2) {
238                if (listeners[i] == TreeModelListener.class) {
239                    // Lazily create the event:
240                    if (e == null) {
241                        e = new TreeModelEvent(this, new Object[] { previousRoot });
242                    }
243                    ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
244                }
245            }
246        }
247    
248        /**
249         * The only event raised by this model is TreeStructureChanged with the root
250         * as path, i.e. the whole tree has changed.
251         */
252        protected void fireRootTreeStructureChanged(Object previousRoot) {
253            // Guaranteed to return a non-null array
254            Object[] listeners = listenerList.getListenerList();
255            TreeModelEvent e = null;
256            // Process the listeners last to first, notifying
257            // those that are interested in this event
258            for (int i = listeners.length - 2; i >= 0; i -= 2) {
259                if (listeners[i] == TreeModelListener.class) {
260                    // Lazily create the event:
261                    if (e == null) {
262                        e = new TreeModelEvent(this, new Object[] { previousRoot });
263                    }
264                    ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
265                }
266            }
267        }
268    
269        protected void fireTreeNodeRemoved(Object[] path, int index, Object child) {
270            fireTreeNodesRemoved(path, new int[] { index }, new Object[] { child });
271        }
272    
273        protected void fireTreeNodeInserted(Object[] path, int index, Object child) {
274            fireTreeNodesInserted(path, new int[] { index }, new Object[] { child });
275        }
276    
277        protected void fireTreeStructureChanged(Object[] path, int index, Object child) {
278            fireTreeStructureChanged(path, new int[] { index }, new Object[] { child });
279        }
280    
281        protected void fireTreeNodeChanged(Object[] path, int index, Object child) {
282            fireTreeNodesChanged(path, new int[] { index }, new Object[] { child });
283        }
284    
285        private class RootHolderChangeHandler implements PropertyChangeListener {
286            public void propertyChange(PropertyChangeEvent evt) {
287                fireRootTreeStructureChanged(root);
288                root = AbstractTreeModel.this.rootHolder.getValue();
289            }
290        }
291    }