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