View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.platform.swing.builder.components;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.concurrent.CopyOnWriteArrayList;
23  
24  import javax.swing.SwingUtilities;
25  import javax.swing.event.TreeModelEvent;
26  import javax.swing.event.TreeModelListener;
27  import javax.swing.tree.TreeModel;
28  import javax.swing.tree.TreePath;
29  
30  import net.sf.jguiraffe.gui.builder.components.model.TreeConfigurationChangeHandler;
31  import net.sf.jguiraffe.gui.builder.components.model.TreeModelChangeListener;
32  
33  import org.apache.commons.configuration.HierarchicalConfiguration;
34  import org.apache.commons.configuration.event.ConfigurationEvent;
35  import org.apache.commons.configuration.event.ConfigurationListener;
36  import org.apache.commons.configuration.tree.ConfigurationNode;
37  import org.apache.commons.lang.ObjectUtils;
38  
39  /**
40   * <p>
41   * A specialized implementation of <code>TreeModel</code> that obtains its data
42   * from a <code>Configuration</code> object.
43   * </p>
44   * <p>
45   * This is a fully functional implementation of Swing's <code>TreeModel</code>
46   * interface. The content of the model is obtained from the nodes stored in a
47   * hierarchical configuration. The tree will display the keys of the
48   * configuration properties, i.e. the names of the nodes.
49   * </p>
50   * <p>
51   * The structure-related methods of the <code>TreeModel</code> interface (e.g.
52   * <code>getRoot()</code> or <code>getChild()</code>) are implemented by
53   * directly forwarding to methods provided by the <code>ConfigurationNode</code>
54   * interface. All of these methods expect that a passed in node (which is of
55   * type <code>Object</code> in the <code>TreeModel</code> interface) can be cast
56   * into a <code>ConfigurationNode</code>.
57   * </p>
58   * <p>
59   * The model also registers itself as event listener at the underlying
60   * configuration and tries to map configuration change events to corresponding
61   * model change events. This is not always possible because configuration change
62   * events often do not contain enough information for such a mapping. If the
63   * mapping is not possible, a very generic structure changed event is fired.
64   * </p>
65   * <p>
66   * As is true for most Swing objects, this class is not thread-safe. It is
67   * possible to manipulate the underlying configuration in a separate thread,
68   * which will cause change events received by this model. These events are then
69   * propagated to registered listeners in the event dispatch thread. However,
70   * this implementation relies on the fact that only a single configuration
71   * change event can be received at a time. (Because typical configuration
72   * implementations cannot be updated concurrently this should not be a
73   * limitation.)
74   * </p>
75   *
76   * @author Oliver Heger
77   * @version $Id: SwingConfigurationTreeModel.java 205 2012-01-29 18:29:57Z oheger $
78   */
79  public class SwingConfigurationTreeModel implements TreeModel,
80          ConfigurationListener, TreeModelChangeListener
81  {
82      /** Stores the underlying configuration object. */
83      private final HierarchicalConfiguration configuration;
84  
85      /** A collection with the event listeners registered for this model. */
86      private final Collection<TreeModelListener> listeners;
87  
88      /** The change handler. */
89      private final TreeConfigurationChangeHandler ccHandler;
90  
91      /**
92       * Creates a new instance of {@code SwingConfigurationTreeModel} and
93       * initializes it with the given {@code HierarchicalConfiguration}
94       * object. Behind the scenes, a {@link TreeConfigurationChangeHandler} is
95       * created and registered at the configuration so that change events can
96       * be correctly processed.
97       *
98       * @param config the configuration (must not be <b>null</b>)
99       * @throws IllegalArgumentException if a required parameter is missing
100      */
101     public SwingConfigurationTreeModel(HierarchicalConfiguration config)
102     {
103         if (config == null)
104         {
105             throw new IllegalArgumentException(
106                     "Configuration must not be null!");
107         }
108 
109         configuration = config;
110         listeners = new CopyOnWriteArrayList<TreeModelListener>();
111         ccHandler = new TreeConfigurationChangeHandler(config, this);
112         configuration.addConfigurationListener(ccHandler);
113     }
114 
115     /**
116      * Returns the configuration object that stores the data of this model.
117      *
118      * @return the underlying configuration object
119      */
120     public HierarchicalConfiguration getConfiguration()
121     {
122         return configuration;
123     }
124 
125     /**
126      * Adds an event listener to this model. The listener must not be
127      * <b>null</b>.
128      *
129      * @param l the listener to add
130      * @throws IllegalArgumentException if the listener is <b>null</b>
131      */
132     public void addTreeModelListener(TreeModelListener l)
133     {
134         if (l == null)
135         {
136             throw new IllegalArgumentException("Listener must not be null!");
137         }
138 
139         listeners.add(l);
140     }
141 
142     /**
143      * Returns the child node of the specified node with the given index. This
144      * implementation expects that the node is of type
145      * <code>ConfigurationNode</code>.
146      *
147      * @param node the node
148      * @param index the index
149      * @return the child node with this index
150      */
151     public Object getChild(Object node, int index)
152     {
153         return ((ConfigurationNode) node).getChild(index);
154     }
155 
156     /**
157      * Returns the number of child nodes of the specified node. This
158      * implementation expects that the node is of type
159      * <code>ConfigurationNode</code>.
160      *
161      * @param node the node
162      * @return the number of child nodes of this node
163      */
164     public int getChildCount(Object node)
165     {
166         return ((ConfigurationNode) node).getChildrenCount();
167     }
168 
169     /**
170      * Returns the index of the specified child node relative to its parent
171      * node. This implementation expects that the node is of type
172      * <code>ConfigurationNode</code>. If the node is no child of the specified
173      * parent, -1 is returned. The parent and the child node can both be
174      * <b>null</b>; then -1 is returned, too.
175      *
176      * @param parent the parent node
177      * @param child the child node
178      * @return the index of this child node or -1
179      */
180     public int getIndexOfChild(Object parent, Object child)
181     {
182         if (parent == null || child == null)
183         {
184             return -1;
185         }
186 
187         int index = 0;
188         for (Object o : ((ConfigurationNode) parent).getChildren())
189         {
190             if (o == child)
191             {
192                 return index;
193             }
194             index++;
195         }
196 
197         return -1;
198     }
199 
200     /**
201      * Returns the root node of this tree model. This is the root node of the
202      * underlying configuration.
203      *
204      * @return the root node of this tree model
205      */
206     public Object getRoot()
207     {
208         return getRootNode();
209     }
210 
211     /**
212      * Tests whether the passed in node is a leaf node. This implementation
213      * expects that the passed in object is a <code>ConfigurationNode</code>. It
214      * then checks whether it has children.
215      *
216      * @param node the node
217      * @return a flag whether this is a leaf node
218      */
219     public boolean isLeaf(Object node)
220     {
221         return ((ConfigurationNode) node).getChildrenCount() == 0;
222     }
223 
224     /**
225      * Removes the specified event listener from this model.
226      *
227      * @param l the listener to be removed
228      */
229     public void removeTreeModelListener(TreeModelListener l)
230     {
231         listeners.remove(l);
232     }
233 
234     /**
235      * The value of a node was changed. This method is called if the user edited
236      * a node in the tree control. This implementation will update the value of
237      * the corresponding configuration node. Then it will fire a change event.
238      *
239      * @param path the path to the changed node
240      * @param newValue the new value
241      */
242     public void valueForPathChanged(TreePath path, Object newValue)
243     {
244         ConfigurationNode node = (ConfigurationNode) path
245                 .getLastPathComponent();
246         if (!ObjectUtils.equals(node.getValue(), newValue))
247         {
248             changeNodeName(node, String.valueOf(newValue));
249             if (path.getPathCount() > 1)
250             {
251                 // It is not the root node
252                 TreeModelEvent event =
253                         new TreeModelEvent(this, path.getParentPath(),
254                                 new int[] {
255                                     getIndexOfChild(node.getParentNode(), node)
256                                 }, new Object[] {
257                                     node
258                                 });
259 
260                 for (TreeModelListener l : listeners)
261                 {
262                     l.treeNodesChanged(event);
263                 }
264             }
265 
266             else
267             {
268                 // fire a generic structure changed event for the root node
269                 fireStructureChangedEvent(getRootNode());
270             }
271         }
272     }
273 
274     /**
275      * The underlying configuration has changed. This method tries to translate
276      * the configuration event into a tree model event. This involves finding
277      * the highest configuration node in the hierarchy affected by this event.
278      * In most cases this will not be possible because the configuration event
279      * typically won't contain enough information. Then a generic structure
280      * changed event for the root node is fired.
281      *
282      * @param event the event
283      * @deprecated This method is no longer used or called. Configuration change
284      * events are now processed by a {@link TreeConfigurationChangeHandler}
285      * and propagated to the {@link #treeModelChanged(ConfigurationNode)}
286      * method.
287      */
288     @Deprecated
289     public void configurationChanged(ConfigurationEvent event)
290     {
291     }
292 
293     /**
294      * The configuration serving as tree model was changed in the sub tree
295      * referenced by the passed in node. This implementation fires a tree
296      * structure change event to all registered listeners.
297      *
298      * @param node the node in the configuration which has changed
299      * @since 1.3
300      */
301     public void treeModelChanged(ConfigurationNode node)
302     {
303         fireStructureChangedEvent(node);
304     }
305 
306     /**
307      * Fires a structure changed event. All registered listeners are notified in
308      * the event dispatch thread.
309      *
310      * @param changedNode the configuration node affected by the change
311      */
312     private void fireStructureChangedEvent(ConfigurationNode changedNode)
313     {
314         final TreeModelEvent event = createEvent(changedNode);
315         SwingUtilities.invokeLater(new Runnable()
316         {
317             public void run()
318             {
319                 for (TreeModelListener l : listeners)
320                 {
321                     l.treeStructureChanged(event);
322                 }
323             }
324         });
325     }
326 
327     /**
328      * Returns the root node of the associated configuration.
329      *
330      * @return the configuration's root node
331      */
332     private ConfigurationNode getRootNode()
333     {
334         return getConfiguration().getRootNode();
335     }
336 
337     /**
338      * Creates a path for the specified configuration node. The path contains
339      * all nodes up to the root node.
340      *
341      * @param node the start node for the path
342      * @return the path
343      */
344     private Object[] createPath(ConfigurationNode node)
345     {
346         List<Object> pathElements = new ArrayList<Object>();
347         ConfigurationNode nd = node;
348 
349         // iterate to the root node
350         while (nd.getParentNode() != null)
351         {
352             pathElements.add(nd);
353             nd = nd.getParentNode();
354         }
355 
356         // Explicitly add the root node. This is a workaround for
357         // inconsistencies
358         // in the handling of parent nodes in hierarchical configurations
359         pathElements.add(getRoot());
360 
361         // now reverse order and create an array
362         Collections.reverse(pathElements);
363         return pathElements.toArray();
364     }
365 
366     /**
367      * Creates an event reporting a change of this tree model. The passed in
368      * node determines the part of the tree structure affected by this change.
369      *
370      * @param changedNode the node affected by the change
371      * @return the event
372      */
373     private TreeModelEvent createEvent(ConfigurationNode changedNode)
374     {
375         Object[] path = createPath(changedNode);
376         return new TreeModelEvent(this, path);
377     }
378 
379     /**
380      * Changes the name of a configuration node. Nodes that have a parent
381      * usually must not be changed. So this method first removes the parent,
382      * then sets the new name, and finally restores the parent.
383      *
384      * @param node the node
385      * @param newName the new name
386      */
387     private void changeNodeName(ConfigurationNode node, String newName)
388     {
389         ccHandler.changeNodeName(node, newName);
390     }
391 }