/*
* @(#)TrdDOMTreeModel.java 3.1 25 Sep 2000
*
*/
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)
{
}
}
}