/* * @(#)TrdDOMTreeModel.java 3.1 25 Sep 2000 * * Copyright 2000 Voquette Inc. All Rights Reserved. * * This software is the proprietary information of VerticalNet, Inc. * Use is subject to license terms. */ import javax.swing.event.*; import javax.swing.tree.*; import java.util.*; import org.jdom.*; /** * This class Reprasents the TreeModel which holds necessary data for tree view. * @version 3.1 25 Sep 2000 * @author Kesav Kumar Kolla, Ruchi Kolla */ public class TrdDOMTreeModel implements TreeModel { private final EventListenerList listenerList = new EventListenerList(); private Document document = null; private final LinkedList undoStack = new LinkedList(); private final LinkedList redoStack = new LinkedList(); /** * Constructor to create the model object * @see TrdDOMTreeModel(Document) */ public TrdDOMTreeModel() { } public TrdDOMTreeModel(Document document) { this.document = document; } /** * Sets the Document object to the model * @param document Document object */ public void setDocument(Document document) { if(document == null) throw new IllegalArgumentException("Root of tree is not allowed to be null"); this.document = document; fireTreeStructureChanged(this, getPathToRoot(document.getRootElement()), null, null); } /** * Returns the Document object * @return Document Object */ public Document getDcoument() { return this.document; } /** * Adds a listener for the TreeModelEvent posted after the tree changes. * * @see #removeTreeModelListener * @param l the listener to add */ public void addTreeModelListener(TreeModelListener l) { listenerList.add(TreeModelListener.class, l); } /** * Removes a listener previously added with addTreeModelListener(). * * @see #addTreeModelListener * @param l the listener to remove */ public void removeTreeModelListener(TreeModelListener l) { listenerList.remove(TreeModelListener.class, l); } /** * Messaged when the user has altered the value for the item identified * by path to newValue. If newValue signifies * a truly new value the model should post a treeNodesChanged * event. * * @param path path to the node that the user has altered. * @param newValue the new value from the TreeCellEditor. */ public void valueForPathChanged(TreePath path, Object newValue) { } /** Returns the child of parent at index index in the parent's * child array. parent must be a node previously obtained from * this data source. This should not return null if index * is a valid index for parent (that is index >= 0 && * index < getChildCount(parent)). * * @return the child of parent at index index * @param parent a node in the tree, obtained from this data source * @param index Index of the Child */ public Object getChild(final Object parent, int index) { final Element element = (Element)parent; final List list = element.getChildren(); int textNode = (element.getTextTrim().length() == 0)?0:1; if(list.isEmpty()) { if(element.getTextTrim().length() > 0) return element.getTextTrim(); } if(index >= list.size()) return element.getTextTrim(); return list.get(index); } /** * Returns the number of children of parent. Returns 0 if the node * is a leaf or if it has no children. parent must be a node * previously obtained from this data source. * * @param parent a node in the tree, obtained from this data source * @return the number of children of the node parent */ public int getChildCount(final Object parent) { final Element element = (Element)parent; final List children = element.getChildren(); int textNode = (element.getTextTrim().length() == 0)?0:1; if(children.isEmpty()) { return textNode; } else return children.size()+textNode; } /** * Returns the index of child in parent. * @param parent Parent Object * @param child Child Object * @return Retunrns the indexof child */ public int getIndexOfChild(Object parent, Object child) { final List children = ((Element)parent).getChildren(); int textNode = (((Element)parent).getTextTrim().length() == 0)?0:1; int index = children.indexOf(child); return index-textNode; } /** * Returns the root of the tree. Returns null only if the tree has * no nodes. * * @return the root of the tree */ public Object getRoot() { Element node = null; try{node = document.getRootElement();}catch(Exception e){} return node; } /** * Returns true if node is a leaf. It is possible for this method * to return false even if node has no children. A directory in a * filesystem, for example, may contain no files; the node representing * the directory is not a leaf, but it also has no children. * * @param node a node in the tree, obtained from this data source * @return true if node is a leaf */ public boolean isLeaf(final Object node) { if(node instanceof String) return true; final Element element = (Element)node; final List children = element.getChildren(); if(children.isEmpty()) return (element.getTextTrim().length() == 0); else return false; } /** * This method is invoked by the TreeCellRenderer for converting the Node to text * Here one can return what ever text reprasentation is required to display the node. * * @param obj The node object * @return text reprasentation of the node */ public String convertValueToText(final Object obj) { if(obj instanceof String) return obj.toString(); if(obj instanceof Element) { final Element element = (Element)obj; final StringBuffer buffer = new StringBuffer(30); buffer.append(element.getQualifiedName()); final List attrs = element.getAttributes(); if(attrs.size() <= 0) return buffer.toString(); buffer.append("["); int i=0; Attribute attr = null; for(i=0; i 0) content.add(index, newChild); else parent.addContent(newChild); int[] newIndexs = new int[1]; newIndexs[0] = index; final Object[] newChildren = new Object[1]; newChildren[0] = newChild; if(!isUndo) addUndoEvent("insertNode", parent, newChild, null, index, null); fireTreeNodesInserted(this, getPathToRoot(parent), newIndexs, newChildren); } /** * Insert child with out undo/redo support * * @see #insertNodeInto(Element, Element, int, boolean) */ public void insertNodeInto(final Element newChild, final Element parent, int index) { insertNodeInto(newChild, parent, index, false); } /** * Inserts a Text Node for the element. This method adds textcontent to the parent element * in the tree. * * @param parent Element to which the text content to be added * @param text Content to be added * @param isUndo Support for undo/redo action */ public void insertTextNode(final Element parent, final String text, boolean isUndo) { if((text == null) || (text.trim().length() <=0)) return; final Element oldElement = (Element)parent.clone(); if(!text.startsWith("<")) parent.addContent(text.trim()); if(!isUndo) addUndoEvent("insertText", parent, null, oldElement, -1, text); fireTreeStructureChanged(this, getPathToRoot(parent.getParent()), null, null); } /** * Insert a text content to the element without any undo/redo support * * @see #insertTextNode(Element, String, boolean) */ public void insertTextNode(final Element parent, final String text) { insertTextNode(parent, text, false); } /** * Moves the node to one level up to the current index. If it is the top most element in the * child list it doesn't do any thing. * * @param element Node to move up * @param isUndo support for undo/redo */ public void moveUp(final Element element, boolean isUndo) { try { if(element == null) return; final Element parent = element.getParent(); if(parent == null) return; final List children = parent.getChildren(); int index = children.indexOf(element); if(index == 0) return; final Object tmp = children.get(index -1); children.set(index-1, element); children.set(index, tmp); fireTreeStructureChanged(this, getPathToRoot(parent), null, null); } catch(Exception ex) { System.err.println(ex); ex.printStackTrace(System.err); } } /** * Moves the node one level up in its child list with out undo/redo support. * * @see #moveUp(Element, boolean) */ public void moveUp(final Element element) { moveUp(element, false); } /** * Moves the node to one level down to the current index. If it is the bottom most element in the * child list it doesn't do any thing. * * @param element Node to move down * @param isUndo support for undo/redo */ public void moveDown(final Element element, boolean isUndo) { try { if(element == null) return; final Element parent = element.getParent(); if(parent == null) return; final List children = parent.getChildren(); int index = children.indexOf(element); if(index == children.size()-1) return; final Object tmp = children.get(index +1); children.set(index+1, element); children.set(index, tmp); fireTreeStructureChanged(this, getPathToRoot(parent), null, null); } catch(Exception ex) { System.err.println(ex); ex.printStackTrace(System.err); } } /** * Moves the node one level down in its child list with out undo/redo support. * * @see #moveDown(Element, boolean) */ public void moveDown(final Element element) { moveDown(element, false); } /* * Notify all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * @see EventListenerList */ protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener)listeners[i+1]).treeStructureChanged(e); } } } /* * Notify all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * @see EventListenerList */ protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e); } } } /* * Notify all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * @see EventListenerList */ protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener)listeners[i+1]).treeNodesInserted(e); } } } /** * Builds the parents of 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. */ protected Element[] getPathToRoot(Element aNode) { return getPathToRoot(aNode, 0); } /** * Builds the parents of 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 */ protected Element[] getPathToRoot(Element aNode, int depth) { // 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. */ Element[] retNodes; if(aNode == null) { if(depth == 0) return null; else retNodes = new Element[depth]; } else { depth++; if(aNode.isRootElement()) //if(aNode.getParent() == null) retNodes = new Element[depth]; else retNodes = getPathToRoot(aNode.getParent(), depth); retNodes[retNodes.length - depth] = aNode; } return retNodes; } /** * Stores the event in the undoevent stack. This method is useful for Undo/Redo actions. * The event stack stores only 5 events. If the stack contains more than 5 events deletes * the first event in the stack. * * @param action The action of the tree change * @param parent Parent Element * @param newElement New Element * @param oldElement Old Element */ private void addUndoEvent(final String action, final Element parent, final Element newElement, final Element oldElement, int index, final String textContent) { if(undoStack.size() >= 5) undoStack.removeFirst(); final UndoableTreeEdit edit = new UndoableTreeEdit(action, parent, newElement, oldElement, index, textContent); undoStack.add(edit); } /** * Stores the event in the redoevent stack. This method is useful for Undo/Redo actions. * The event stack stores only 5 events. If the stack contains more than 5 events deletes * the first event in the stack. * * @param action The action of the tree change * @param parent Parent Element * @param newElement New Element * @param oldElement Old Element */ private void addRedoEvent(final UndoableTreeEdit edit) { if(undoStack.size() >= 5) undoStack.removeFirst(); redoStack.add(edit); } /** * Undos the previous action. */ public void undo() { try { final UndoableTreeEdit edit = (UndoableTreeEdit)undoStack.removeLast(); if(edit == null) return; addRedoEvent(edit); if(edit.getAction().equals("removeNode")) { final Element parent = edit.getParent(); final Element oldElement = edit.getOldElement(); int index = edit.getIndex(); insertNodeInto(oldElement, parent, index, true); return; } if(edit.getAction().equals("removeText")) { final Element oldElement = edit.getOldElement(); final Element newElement = edit.getNewElement(); changeNode(newElement, oldElement, true); return; } if(edit.getAction().equals("changeNode")) { final Element oldElement = edit.getOldElement(); final Element newElement = edit.getNewElement(); changeNode(newElement, oldElement, true); return; } if(edit.getAction().equals("insertNode")) { final Element parent = edit.getParent(); final Element newElement = edit.getNewElement(); removeNodeFromParent(newElement, true); return; } if(edit.getAction().equals("insertText")) { final Element parent = edit.getParent(); removeNodeText(parent, true); return; } } catch(Exception ex) { return; } } /** * Redos the previous action. */ public void redo() { try { final UndoableTreeEdit edit = (UndoableTreeEdit)redoStack.removeLast(); if(edit == null) return; if(edit.getAction().equals("removeNode")) { final Element parent = edit.getParent(); final Element oldElement = edit.getOldElement(); removeNodeFromParent(oldElement); return; } if(edit.getAction().equals("removeText")) { final Element oldElement = edit.getOldElement(); removeNodeText(oldElement); return; } if(edit.getAction().equals("changeNode")) { final Element oldElement = edit.getOldElement(); final Element newElement = edit.getNewElement(); changeNode(oldElement, newElement); return; } if(edit.getAction().equals("insertNode")) { final Element parent = edit.getParent(); final Element newElement = edit.getNewElement(); insertNodeInto(newElement, parent, edit.getIndex()); return; } if(edit.getAction().equals("insertText")) { final Element parent = edit.getParent(); insertTextNode(parent, edit.getTextContent()); return; } } catch(Exception ex) { } } }