001    /*
002     * $Id: DefaultTreeTableModel.java 3187 2009-01-20 16:19:08Z kschaefe $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     *
012     * This library 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 GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx.treetable;
022    
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import javax.swing.tree.TreePath;
027    
028    /**
029     * {@code DefaultTreeTableModel} is a concrete implementation of
030     * {@code AbstractTreeTableModel} and is provided purely as a convenience.
031     * Applications that use {@code JXTreeTable} are expected to provide their own
032     * implementation of a {@code TreeTableModel}.
033     * <p>
034     * The {@code DefaultTreeTableModel} is designed to be used with
035     * {@code TreeTableNode}s. Specifically, users should extend
036     * {@code AbstractMutableTreeTableNode} to provide custom implementations for
037     * data display.
038     * <p>
039     * Users who do not provide a list of column identifiers must provide a root
040     * that contains at least one column. Without specified identifiers the model
041     * will attempt to calculate the columns required for display by querying the
042     * root node. Normally, the root node can be little more than a shell (in
043     * displays that hide it), but without identifiers, the model relies on the root
044     * node metadata for display.
045     * 
046     * @author Ramesh Gupta
047     * @author Karl Schaefer
048     */
049    public class DefaultTreeTableModel extends AbstractTreeTableModel {
050        /** The <code>List</code> of column identifiers. */
051        protected List<?> columnIdentifiers;
052    
053        private boolean useAutoCalculatedIdentifiers;
054    
055        /**
056         * Creates a new {@code DefaultTreeTableModel} with a {@code null} root.
057         */
058        public DefaultTreeTableModel() {
059            this(null);
060        }
061    
062        /**
063         * Creates a new {@code DefaultTreeTableModel} with the specified
064         * {@code root}.
065         * 
066         * @param root
067         *            the root node of the tree
068         */
069        public DefaultTreeTableModel(TreeTableNode root) {
070            this(root, null);
071        }
072    
073        /**
074         * Creates a new {@code DefaultTreeTableModel} with the specified {@code
075         * root} and column names.
076         * 
077         * @param root
078         *            the root node of the tree
079         * @param columnNames
080         *            the names of the columns used by this model
081         * @see #setColumnIdentifiers(List)
082         */
083        public DefaultTreeTableModel(TreeTableNode root, List<?> columnNames) {
084            super(root);
085            
086            setColumnIdentifiers(columnNames);
087        }
088    
089        private boolean isValidTreeTableNode(Object node) {
090             boolean result = false;
091    
092            if (node instanceof TreeTableNode) {
093                TreeTableNode ttn = (TreeTableNode) node;
094    
095                while (!result && ttn != null) {
096                    result = ttn == root;
097    
098                    ttn = ttn.getParent();
099                }
100            }
101    
102            return result;
103        }
104    
105        /**
106         * Replaces the column identifiers in the model. If the number of
107         * <code>newIdentifier</code>s is greater than the current number of
108         * columns, new columns are added to the end of each row in the model. If
109         * the number of <code>newIdentifier</code>s is less than the current
110         * number of columns, all the extra columns at the end of a row are
111         * discarded.
112         * <p>
113         * 
114         * @param columnIdentifiers
115         *            vector of column identifiers. If <code>null</code>, set the
116         *            model to zero columns
117         */
118        // from DefaultTableModel
119        public void setColumnIdentifiers(List<?> columnIdentifiers) {
120            useAutoCalculatedIdentifiers = columnIdentifiers == null;
121    
122            this.columnIdentifiers = useAutoCalculatedIdentifiers
123                    ? getAutoCalculatedIdentifiers(getRoot())
124                    : columnIdentifiers;
125                    
126            modelSupport.fireNewRoot();
127        }
128    
129        private static List<String> getAutoCalculatedIdentifiers(
130                TreeTableNode exemplar) {
131            List<String> autoCalculatedIndentifiers = new ArrayList<String>();
132    
133            if (exemplar != null) {
134                for (int i = 0, len = exemplar.getColumnCount(); i < len; i++) {
135                    // forces getColumnName to use super.getColumnName
136                    autoCalculatedIndentifiers.add(null);
137                }
138            }
139    
140            return autoCalculatedIndentifiers;
141        }
142        
143        /**
144         * Returns the root of the tree. Returns {@code null} only if the tree has
145         * no nodes.
146         * 
147         * @return the root of the tree
148         * 
149         * @throws ClassCastException
150         *             if {@code root} is not a {@code TreeTableNode}. Even though
151         *             subclasses have direct access to {@code root}, they should
152         *             avoid accessing it directly.
153         * @see AbstractTreeTableModel#root
154         * @see #setRoot(TreeTableNode)
155         */
156        @Override
157        public TreeTableNode getRoot() {
158            return (TreeTableNode) root;
159        }
160    
161        /**
162         * Gets the value for the {@code node} at {@code column}.
163         * 
164         * @impl delegates to {@code TreeTableNode.getValueAt(int)}
165         * @param node
166         *            the node whose value is to be queried
167         * @param column
168         *            the column whose value is to be queried
169         * @return the value Object at the specified cell
170         * @throws IllegalArgumentException
171         *             if {@code node} is not an instance of {@code TreeTableNode}
172         *             or is not managed by this model, or {@code column} is not a
173         *             valid column index
174         */
175        public Object getValueAt(Object node, int column) {
176            if (!isValidTreeTableNode(node)) {
177                throw new IllegalArgumentException(
178                        "node must be a valid node managed by this model");
179            }
180    
181            if (column < 0 || column >= getColumnCount()) {
182                throw new IllegalArgumentException("column must be a valid index");
183            }
184    
185            TreeTableNode ttn = (TreeTableNode) node;
186            
187            if (column >= ttn.getColumnCount()) {
188                return null;
189            }
190            
191            return ttn.getValueAt(column);
192        }
193    
194        /**
195         * {@inheritDoc}
196         */
197        @Override
198        public void setValueAt(Object value, Object node, int column) {
199            if (!isValidTreeTableNode(node)) {
200                throw new IllegalArgumentException(
201                        "node must be a valid node managed by this model");
202            }
203    
204            if (column < 0 || column >= getColumnCount()) {
205                throw new IllegalArgumentException("column must be a valid index");
206            }
207    
208            TreeTableNode ttn = (TreeTableNode) node;
209    
210            if (column < ttn.getColumnCount()) {
211                ttn.setValueAt(value, column);
212    
213                modelSupport.firePathChanged(new TreePath(getPathToRoot(ttn)));
214            }
215        }
216    
217        /**
218         * {@inheritDoc}
219         */
220        public int getColumnCount() {
221            return columnIdentifiers.size();
222        }
223    
224        /**
225         * {@inheritDoc}
226         */
227        // Can we make getColumnClass final and avoid the complex DTM copy? -- kgs
228        @Override
229        public String getColumnName(int column) {
230            // Copied from DefaultTableModel.
231            Object id = null;
232    
233            // This test is to cover the case when
234            // getColumnCount has been subclassed by mistake ...
235            if (column < columnIdentifiers.size() && (column >= 0)) {
236                id = columnIdentifiers.get(column);
237            }
238    
239            return (id == null) ? super.getColumnName(column) : id.toString();
240        }
241    
242        /**
243         * {@inheritDoc}
244         */
245        public Object getChild(Object parent, int index) {
246            if (!isValidTreeTableNode(parent)) {
247                throw new IllegalArgumentException(
248                        "parent must be a TreeTableNode managed by this model");
249            }
250    
251            return ((TreeTableNode) parent).getChildAt(index);
252        }
253    
254        /**
255         * {@inheritDoc}
256         */
257        public int getChildCount(Object parent) {
258            if (!isValidTreeTableNode(parent)) {
259                throw new IllegalArgumentException(
260                        "parent must be a TreeTableNode managed by this model");
261            }
262    
263            return ((TreeTableNode) parent).getChildCount();
264        }
265    
266        /**
267         * {@inheritDoc}
268         */
269        public int getIndexOfChild(Object parent, Object child) {
270            if (!isValidTreeTableNode(parent) || !isValidTreeTableNode(child)) {
271                return -1;
272            }
273    
274            return ((TreeTableNode) parent).getIndex((TreeTableNode) child);
275        }
276    
277        /**
278         * {@inheritDoc}
279         */
280        @Override
281        public boolean isCellEditable(Object node, int column) {
282            if (!isValidTreeTableNode(node)) {
283                throw new IllegalArgumentException(
284                        "node must be a valid node managed by this model");
285            }
286    
287            if (column < 0 || column >= getColumnCount()) {
288                throw new IllegalArgumentException("column must be a valid index");
289            }
290    
291            TreeTableNode ttn = (TreeTableNode) node;
292    
293            if (column >= ttn.getColumnCount()) {
294                return false;
295            }
296    
297            return ttn.isEditable(column);
298        }
299    
300        /**
301         * {@inheritDoc}
302         */
303        @Override
304        public boolean isLeaf(Object node) {
305            if (!isValidTreeTableNode(node)) {
306                throw new IllegalArgumentException(
307                        "node must be a TreeTableNode managed by this model");
308            }
309    
310            return ((TreeTableNode) node).isLeaf();
311        }
312    
313        /**
314         * Gets the path from the root to the specified node.
315         * 
316         * @param aNode
317         *            the node to query
318         * @return an array of {@code TreeTableNode}s, where
319         *         {@code arr[0].equals(getRoot())} and
320         *         {@code arr[arr.length - 1].equals(aNode)}, or an empty array if
321         *         the node is not found.
322         * @throws NullPointerException
323         *             if {@code aNode} is {@code null}
324         */
325        public TreeTableNode[] getPathToRoot(TreeTableNode aNode) {
326            List<TreeTableNode> path = new ArrayList<TreeTableNode>();
327            TreeTableNode node = aNode;
328    
329            while (node != root) {
330                path.add(0, node);
331    
332                node = (TreeTableNode) node.getParent();
333            }
334    
335            if (node == root) {
336                path.add(0, node);
337            }
338    
339            return path.toArray(new TreeTableNode[0]);
340        }
341    
342        /**
343         * Sets the root for this table model. If no column identifiers have been
344         * specified, this will rebuild the identifier list, using {@code root} as
345         * an examplar of the table.
346         * 
347         * @param root
348         *            the node to set as root
349         */
350        public void setRoot(TreeTableNode root) {
351            this.root = root;
352    
353            if (useAutoCalculatedIdentifiers) {
354                // rebuild the list
355                //this already fires an event don't duplicate
356                setColumnIdentifiers(null);
357            } else {
358                modelSupport.fireNewRoot();
359            }
360        }
361    
362        /**
363         * Invoked this to insert newChild at location index in parents children.
364         * This will then message nodesWereInserted to create the appropriate event.
365         * This is the preferred way to add children as it will create the
366         * appropriate event.
367         */
368        public void insertNodeInto(MutableTreeTableNode newChild,
369                MutableTreeTableNode parent, int index) {
370            parent.insert(newChild, index);
371    
372            modelSupport.fireChildAdded(new TreePath(getPathToRoot(parent)), index,
373                    newChild);
374        }
375    
376        /**
377         * Message this to remove node from its parent. This will message
378         * nodesWereRemoved to create the appropriate event. This is the preferred
379         * way to remove a node as it handles the event creation for you.
380         */
381        public void removeNodeFromParent(MutableTreeTableNode node) {
382            MutableTreeTableNode parent = (MutableTreeTableNode) node.getParent();
383    
384            if (parent == null) {
385                throw new IllegalArgumentException("node does not have a parent.");
386            }
387    
388            int index = parent.getIndex(node);
389            node.removeFromParent();
390    
391            modelSupport.fireChildRemoved(new TreePath(getPathToRoot(parent)),
392                    index, node);
393        }
394    
395        /**
396         * Called when value for the item identified by path has been changed. If
397         * newValue signifies a truly new value the model should post a {@code
398         * treeNodesChanged} event.
399         * <p>
400         * This changes the object backing the {@code TreeTableNode} described by
401         * the path. This change does not alter a nodes children in any way. If you
402         * need to change structure of the node, use one of the provided mutator
403         * methods.
404         * 
405         * @param path
406         *            path to the node that has changed
407         * @param newValue
408         *            the new value
409         * @throws NullPointerException
410         *             if {@code path} is {@code null}
411         * @throws IllegalArgumentException
412         *             if {@code path} is not a path managed by this model
413         * @throws ClassCastException
414         *             if {@code path.getLastPathComponent()} is not a {@code
415         *             TreeTableNode}
416         */
417        @Override
418        public void valueForPathChanged(TreePath path, Object newValue) {
419            if (path.getPathComponent(0) != root) {
420                throw new IllegalArgumentException("invalid path");
421            }
422            
423            TreeTableNode node = (TreeTableNode) path.getLastPathComponent();
424            node.setUserObject(newValue);
425            
426            modelSupport.firePathChanged(path);
427        }
428    
429        /**
430         * Sets the user object for a node. Client code must use this method, so
431         * that the model can notify listeners that a change has occurred.
432         * <p>
433         * This method is a convenient cover for
434         * {@link #valueForPathChanged(TreePath, Object)}.
435         * 
436         * @param node
437         *            the node to modify
438         * @param userObject
439         *            the new user object to set
440         * @throws NullPointerException
441         *             if {@code node} is {@code null}
442         * @throws IllegalArgumentException
443         *             if {@code node} is not a node managed by this model
444         */
445        public void setUserObject(TreeTableNode node, Object userObject) {
446            valueForPathChanged(new TreePath(getPathToRoot(node)), userObject);
447        }
448    }