/* * Copyright 1997-1999 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or * intended for use in the design, construction, operation or * maintenance of any nuclear facility. */ import java.lang.reflect.*; import javax.swing.tree.*; /** * An implementation of TreeTableModel that uses reflection to answer * TableModel methods. This works off a handful * of values. A TreeNode is used to answer all the TreeModel related * methods (similiar to AbstractTreeTableModel and DefaultTreeModel). * The column names are specified in the constructor. The values for * the columns are dynamically obtained via reflection, you simply * provide the method names. The methods used to set a particular value are * also specified as an array of method names, a null method name, or * null array indicates the column isn't editable. And the class types, * used for the TableModel method getColumnClass are specified in the * constructor. * * @author Scott Violet */ public class DynamicTreeTableModel extends AbstractTreeTableModel { /** Names of the columns, used for the TableModel getColumnName method. */ private String[] columnNames; /** Method names used to determine a particular value. Used for the * TableModel method getValueAt. */ private String[] methodNames; /** Setter method names, used to set a particular value. Used for the * TableModel method setValueAt. A null entry, or array, indicates the * column is not editable. */ private String[] setterMethodNames; /** Column classes, used for the TableModel method getColumnClass. */ private Class[] cTypes; /** * Constructor for creating a DynamicTreeTableModel. */ public DynamicTreeTableModel(TreeNode root, String[] columnNames, String[] getterMethodNames, String[] setterMethodNames, Class[] cTypes) { super(root); this.columnNames = columnNames; this.methodNames = getterMethodNames; this.setterMethodNames = setterMethodNames; this.cTypes = cTypes; } // // TreeModel interface // /** * TreeModel method to return the number of children of a particular * node. Since node is a TreeNode, this can be answered * via the TreeNode method getChildCount. */ public int getChildCount(Object node) { return ((TreeNode)node).getChildCount(); } /** * TreeModel method to locate a particular child of the specified * node. Since node is a TreeNode, this can be answered * via the TreeNode method getChild. */ public Object getChild(Object node, int i) { return ((TreeNode)node).getChildAt(i); } /** * TreeModel method to determine if a node is a leaf. * Since node is a TreeNode, this can be answered * via the TreeNode method isLeaf. */ public boolean isLeaf(Object node) { return ((TreeNode)node).isLeaf(); } // // The TreeTable interface. // /** * Returns the number of column names passed into the constructor. */ public int getColumnCount() { return columnNames.length; } /** * Returns the column name passed into the constructor. */ public String getColumnName(int column) { if (cTypes == null || column < 0 || column >= cTypes.length) { return null; } return columnNames[column]; } /** * Returns the column class for column column. This * is set in the constructor. */ public Class getColumnClass(int column) { if (cTypes == null || column < 0 || column >= cTypes.length) { return null; } return cTypes[column]; } /** * Returns the value for the column column and object * node. The return value is determined by invoking * the method specified in constructor for the passed in column. */ public Object getValueAt(Object node, int column) { try { Method method = node.getClass().getMethod(methodNames[column], null); if (method != null) { return method.invoke(node, null); } } catch (Throwable th) {} return null; } /** * Returns true if there is a setter method name for column * column. This is set in the constructor. */ public boolean isCellEditable(Object node, int column) { return (setterMethodNames != null && setterMethodNames[column] != null); } /** * Sets the value to aValue for the object * node in column column. This is done * by using the setter method name, and coercing the passed in * value to the specified type. */ // Note: This looks up the methods each time! This is rather inefficient; // it should really be changed to cache matching methods/constructors // based on node's class, and aValue's class. public void setValueAt(Object aValue, Object node, int column) { boolean found = false; try { // We have to search through all the methods since the // types may not match up. Method[] methods = node.getClass().getMethods(); for (int counter = methods.length - 1; counter >= 0; counter--) { if (methods[counter].getName().equals (setterMethodNames[column]) && methods[counter]. getParameterTypes() != null && methods[counter]. getParameterTypes().length == 1) { // We found a matching method Class param = methods[counter].getParameterTypes()[0]; if (!param.isInstance(aValue)) { // Yes, we can use the value passed in directly, // no coercision is necessary! if (aValue instanceof String && ((String)aValue).length() == 0) { // Assume an empty string is null, this is // probably bogus for here. aValue = null; } else { // Have to attempt some sort of coercision. // See if the expected parameter type has // a constructor that takes a String. Constructor cs = param.getConstructor (new Class[] { String.class }); if (cs != null) { aValue = cs.newInstance(new Object[] { aValue }); } else { aValue = null; } } } // null either means it was an empty string, or there // was no translation. Could potentially deal with these // differently. methods[counter].invoke(node, new Object[] { aValue }); found = true; break; } } } catch (Throwable th) { System.out.println("exception: " + th); } if (found) { // The value changed, fire an event to notify listeners. TreeNode parent = ((TreeNode)node).getParent(); fireTreeNodesChanged(this, getPathToRoot(parent), new int[] { getIndexOfChild(parent, node) }, new Object[] { node }); } } /** * Builds the parents of the node up to and including the root node, * where the original node is the last element in the returned array. * The length of the returned array gives the node's depth in the * tree. * * @param aNode the TreeNode to get the path for * @param an array of TreeNodes giving the path from the root to the * specified node. */ public TreeNode[] getPathToRoot(TreeNode aNode) { return getPathToRoot(aNode, 0); } /** * Builds the parents of the node up to and including the root node, * where the original node is the last element in the returned array. * The length of the returned array gives the node's depth in the * tree. * * @param aNode the TreeNode to get the path for * @param depth an int giving the number of steps already taken towards * the root (on recursive calls), used to size the returned array * @return an array of TreeNodes giving the path from the root to the * specified node */ private TreeNode[] getPathToRoot(TreeNode aNode, int depth) { TreeNode[] retNodes; // This method recurses, traversing towards the root in order // size the array. On the way back, it fills in the nodes, // starting from the root and working back to the original node. /* Check for null, in case someone passed in a null node, or they passed in an element that isn't rooted at root. */ if(aNode == null) { if(depth == 0) return null; else retNodes = new TreeNode[depth]; } else { depth++; if(aNode == root) retNodes = new TreeNode[depth]; else retNodes = getPathToRoot(aNode.getParent(), depth); retNodes[retNodes.length - depth] = aNode; } return retNodes; } }