/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing;
import java.util.Arrays;
import java.util.Enumeration;
import javax.swing.tree.TreeNode;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;
import org.geotoolkit.resources.Errors;
import org.apache.sis.util.Classes;
import org.geotoolkit.gui.swing.tree.TreeTableNode;
/**
* A {@link org.jdesktop.swingx.treetable.TreeTableModel} using the Geotk
* {@link org.geotoolkit.gui.swing.tree.TreeTableNode}. This is called
* "<cite>Adapter</cite>" because it adapts a Geotk {@code TreeTableNode} for use
* with <cite>SwingX</cite> {@link org.jdesktop.swingx.treetable.TreeTableNode}.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.04
*
* @since 3.04
* @module
*/
public class TreeTableModelAdapter extends AbstractTreeTableModel {
/**
* The most specific superclass for all cell values in the columns, or {@code null}
* if not yet determined. The length of this array is the number of columns.
*/
private Class<?>[] types;
/**
* Creates a new tree table model with no root. The {@link #setRoot setRoot}
* method must be invoked before this instance can be used.
*/
public TreeTableModelAdapter() {
}
/**
* Creates a new tree table model having the given root.
*
* @param root The root of the tree table model.
*/
public TreeTableModelAdapter(final TreeTableNode root) {
super(root);
}
/**
* Returns the root of the tree.
*
* @return The root of the tree.
*/
@Override
public TreeTableNode getRoot() {
return (TreeTableNode) super.getRoot();
}
/**
* Sets a new root for this table model.
*
* @param root The new root.
*/
public void setRoot(final TreeTableNode root) {
if (root != this.root) {
this.root = root;
types = null;
modelSupport.fireNewRoot();
}
}
/**
* Returns {@code true} if the given node belong to this tree model.
*/
private boolean isOwner(TreeNode node) {
while (node != null) {
if (node == root) {
return true;
}
node = node.getParent();
}
return false;
}
/**
* Returns the most specific superclass for all cell values in the columns.
* The length of this array is the number of columns.
*
* @return The types. This array is not cloned - modify only if the changes
* are aimed to be permanent.
*/
private Class<?>[] types() {
if (types == null) {
if ((types = types(getRoot(), null)) == null) {
types = new Class<?>[0];
}
}
return types;
}
/**
* Returns the most specific superclass for all cell values in the columns, or {@code null}
* if there is no node. This method invokes itself recursively for every children.
*
* @param node The parent node.
* @param types The array to update.
* @return The updated array.
*/
private static Class<?>[] types(final TreeTableNode node, Class<?>[] types) {
if (node != null) {
final int nc = node.getColumnCount();
if (types == null) {
types = new Class<?>[nc];
} else if (types.length < nc) {
types = Arrays.copyOf(types, nc);
}
for (int i=0; i<nc; i++) {
types[i] = Classes.findCommonClass(types[i], node.getColumnClass(i));
}
for (final Enumeration<? extends TreeNode> it=node.children(); it.hasMoreElements();) {
types = types((TreeTableNode) it.nextElement(), types);
}
}
return types;
}
/**
* Returns the number of columns in the model. The number of columns shall include
* the column used for displaying the tree.
*
* @return The number of columns in the model.
*/
@Override
public int getColumnCount() {
return types().length;
}
/**
* Returns the most specific superclass for all the cell values in the column.
*
* @param column The index of the column.
* @return The common ancestor class of the object values in the model.
*/
@Override
public Class<?> getColumnClass(int column) {
return types()[column];
}
/**
* Casts the given object to a {@code TreeTableNode}, or thrown an exception if it is
* not of the expected type.
*/
private static TreeTableNode cast(final Object node) throws IllegalArgumentException {
if (node instanceof TreeTableNode) {
return (TreeTableNode) node;
}
throw new IllegalArgumentException(node == null ?
Errors.format(Errors.Keys.NullArgument_1, "node") :
Errors.format(Errors.Keys.IllegalArgumentClass_3, "node", node.getClass(), TreeTableNode.class));
}
/**
* Gets the value for the given node at given column.
*
* @param node The node whose value is to be queried.
* @param column The column whose value is to be queried.
* @return The value at the specified cell, or {@code null} if none.
* @throws IllegalArgumentException If the given node is not an instance of {@link TreeTableNode}.
*/
@Override
public Object getValueAt(final Object node, final int column) {
final TreeTableNode tn = cast(node);
try {
return tn.getValueAt(column);
} catch (IndexOutOfBoundsException e) {
if (column >= 0 && column < getColumnCount()) {
return null;
}
throw e;
}
}
/**
* Sets the value for the given node at the given column index.
*
* @param value The new value.
* @param node The node whose value is to be changed.
* @param column The column whose value is to be changed.
* @throws IllegalArgumentException If the given node is not an instance of {@link TreeTableNode}.
*/
@Override
public void setValueAt(final Object value, final Object node, final int column) {
cast(node).setValueAt(value, column);
}
/**
* Returns {@code true} if the given node is a leaf.
*
* @param node A node in the tree.
* @return {@code true} if the given node is a leaf.
* @throws IllegalArgumentException If the given node is not an instance of {@link TreeTableNode}.
*/
@Override
public boolean isLeaf(final Object node) {
return cast(node).isLeaf();
}
/**
* Returns the number of children of parent. Returns 0 if the node is a leaf or if
* it has no children.
*
* @param node The parent node in the tree.
* @return The number of children of the given node.
* @throws IllegalArgumentException If the given node is not an instance of {@link TreeTableNode}.
*/
@Override
public int getChildCount(final Object node) {
return cast(node).getChildCount();
}
/**
* Returns the child of the given node at index index in the node's child array.
*
* @param node The parent node in the tree.
* @param index Index of the child to get.
* @return The child at the given index.
* @throws IllegalArgumentException If the given node is not an instance of {@link TreeTableNode}.
*/
@Override
public TreeNode getChild(final Object node, int index) {
return cast(node).getChildAt(index);
}
/**
* Returns the index of the given child in the given node. If either the node or the child
* is null, or if the node or the child don't belong to this tree model, returns {@code -1}.
*
* @param node A note in the tree.
* @param child The node we are interested in.
* @return The index of the child in the node, or -1.
*/
@Override
public int getIndexOfChild(final Object node, final Object child) {
if (node instanceof TreeTableNode && child instanceof TreeNode) {
final TreeTableNode tn = (TreeTableNode) node;
if (isOwner(tn)) {
return tn.getIndex((TreeNode) child);
}
}
return -1;
}
}