XmlTreeModel.java

/*
 * Copyright (c) 2003-2004 by Woehrmann Softwareentwicklung
 *
 * Woehrmann Softwareentwicklung (hereinafter referred to as "Developer")
 * grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that Licensee does not utilize the software in a manner
 * which is disparaging to the Developer.
 *
 * 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. THE DEVELOPER SHALL NOT BE LIABLE
 * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL THE DEVELOPER
 * 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 SOFTWARE, EVEN IF THE DEVELOPER HAS BEEN ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGES.
 *
 */

import java.util.*;

import javax.swing.event.*;
import javax.swing.tree.*;

import org.w3c.dom.*;

public class XmlTreeModel implements TreeModel
{ private XmlTreeNodeFilter treeNodeFilter;
  private Vector treeModelListeners;
  private Node rootNode;

/**
 * Creates a new <code>XmlTreeModel</code>.
 *
 * @param rootNode as the root-node of the XML structure.
 */
  public XmlTreeModel(Node rootNode)
  { this(rootNode, null);
  }

/**
 * Creates a new <code>XMLDocumentTree</code>.
 *
 * @param rootNode as the root-node of the XML structure.
 * @param filter that defines what tree nodes should be visible or not.
 */
  public XmlTreeModel(Node rootNode, XmlTreeNodeFilter  filter)
  { this.rootNode = rootNode;
    treeModelListeners = new Vector();
    setTreeNodeFilter(filter);
  }

/**
 * Sets the tree node filter for this tree model.
 *
 * @param filter that defines what tree nodes should be visible or not.
 */
  public void setTreeNodeFilter(XmlTreeNodeFilter  filter)
  { this.treeNodeFilter = filter;
  }

//=============================================================================
// Implementing the TreeModel-Interface
//-----------------------------------------------------------------------------

/**
 * Adds a listener for the TreeModelEvent posted after the tree changes.
 *
 * @param listener as the TreeModelListener.
 */
  public void addTreeModelListener(TreeModelListener listener)
  { if (listener != null && !treeModelListeners.contains(listener))
    { treeModelListeners.addElement(listener);
    }
  }

/**
 * Removes a listener from the listeners list.
 *
 * @param listener as the TreeModelListener.
 */
  public void removeTreeModelListener(TreeModelListener listener)
  { if (listener != null) treeModelListeners.removeElement(listener);
  }

/**
 * Returns the root node of the tree.
 */
  public Object getRoot()
  { return rootNode;
  }

/**
 * Returns false if node has visible childs, else true.
 */
  public boolean isLeaf(Object node)
  { return getVisibleChildCount((Node)node) == 0;
  }

/**
 * Returns the child XMLElement of parent XMLElement at index index.
 */
  public Object getChild(Object parent, int index)
  { return getVisibleChildAt((Node)parent, index);
  }

/**
 * Returns the number of children of parent.
 */
  public int getChildCount(Object parent)
  { return getVisibleChildCount((Node)parent);
  }

/**
 * Returns the number of children of parent.
 */
  public int getIndexOfChild(Object parent, Object child)
  { return getIndexOfVisibleChild((Node)parent, (Node)child);
  }

/**
 * Not implemented in this TreeModel.
 */
  public void valueForPathChanged(TreePath path, Object newValue)
  { /* not implemented */
  }

//=============================================================================
// Internal filter support ...
//-----------------------------------------------------------------------------

  private boolean isVisibleXmlTreeNode(Node node)
  { return (treeNodeFilter == null) ? true : treeNodeFilter.isVisibleXmlTreeNode(node);
  }

  private Node getVisibleChildAt(Node parentNode, int index)
  { NodeList childNodes = parentNode.getChildNodes();
    for (int i = 0, visibleIndex = 0, n = childNodes.getLength(); i < n; i++)
    { Node childNode = childNodes.item(i);
      if (!isVisibleXmlTreeNode(childNode)) continue;
      if (index == visibleIndex) return childNode;
      visibleIndex++;
    }
    return null;
  }

  private int getVisibleChildCount(Node parentNode)
  { NodeList childNodes = parentNode.getChildNodes();
    int visibleChildCount = 0;
    for (int i = 0, n = childNodes.getLength(); i < n; i++)
    { if (isVisibleXmlTreeNode(childNodes.item(i))) visibleChildCount++;
    }
    return visibleChildCount;
  }

  private int getIndexOfVisibleChild(Node parentNode, Node childNode)
  { NodeList childNodes = parentNode.getChildNodes();
    for (int i = 0, visibleChildCount = 0, n = childNodes.getLength(); i < n; i++)
    { Node checkNode = childNodes.item(i);
      if (isVisibleXmlTreeNode(checkNode))
      { if (checkNode.equals(childNode)) return visibleChildCount;
        visibleChildCount++;
      }
    }
    return -1;
  }

//=============================================================================
// Private Tree-Model Event support ...
//-----------------------------------------------------------------------------

  private void fireTreeNodesChanged(TreeModelEvent e)
  { for (int i = 0, n = treeModelListeners.size(); i < n; i++)
    { ((TreeModelListener)treeModelListeners.elementAt(i)).treeNodesChanged(e);
    }
  }

  private void fireTreeNodesInserted(TreeModelEvent e)
  { for (int i = 0, n = treeModelListeners.size(); i < n; i++)
    { ((TreeModelListener)treeModelListeners.elementAt(i)).treeNodesInserted(e);
    }
  }

  private void fireTreeNodesRemoved(TreeModelEvent e)
  { for (int i = 0, n = treeModelListeners.size(); i < n; i++)
    { ((TreeModelListener)treeModelListeners.elementAt(i)).treeNodesRemoved(e);
    }
  }

  private void fireTreeStructureChanged(TreeModelEvent e)
  { for (int i = 0, n = treeModelListeners.size(); i < n; i++)
    { ((TreeModelListener)treeModelListeners.elementAt(i)).treeStructureChanged(e);
    }
  }

  private void fireTreeNodeInserted(Node parentNode, Node childNode)
  { TreeModelEvent e = makeTreeModelChangeEvent(parentNode, childNode);
    fireTreeNodesInserted(e);
  }

  private TreeModelEvent makeTreeModelChangeEvent(Node parentNode, Node childNode)
  { return new TreeModelEvent(
      this,
      getPathToRoot( parentNode ),
      new int[]    { getIndexOfVisibleChild(parentNode, childNode) },
      new Object[] { childNode }
    );
  }

//=============================================================================
// Tree-Path and -Node support ...
//-----------------------------------------------------------------------------

/**
 * Provides an array of Nodes as path from root to node.
 *
 * @param node as the path delimited Node
 * @return Object[] as path where Object[0] is the root node and Object[size - 1] is the given node.
 */
  public Object[] getPathToRoot(Node node)
  { Vector pathNodes = new Vector();
    Node parentNode = node;
    while (parentNode != null)
    { pathNodes.add(0, parentNode);
      if (parentNode.equals(rootNode)) break;
      parentNode = parentNode.getParentNode();
    }
    return (parentNode.equals(rootNode)) ? pathNodes.toArray() : new Object[0];
  }

/**
 * Returns the number of siblings of child (inlc. child). This is the number of childs of childs parent.
 *
 * @return number of siblings or -1 if child is the root element.
 */
  public int getSiblingsCount(Node child)
  { Node parent = child.getParentNode();
    if (parent != null) return getChildCount(parent);
    return -1;
  }

//=============================================================================
// Tree-Event support ...
//-----------------------------------------------------------------------------

/**
 * Notifies all listeners that have registered interest for notification on changes.
 *
 * @param changeNode as Node whose underlying structure has changed.
 */
  public void fireTreeStructureChanged(Node changedNode)
  { fireTreeStructureChanged(new TreeModelEvent(this, getPathToRoot(changedNode)));
  }

/**
 * Notifies all listeners that have registered interest for notification on changes.
 *
 * @param treeNode as Node whose content has changed.
 */
  public void fireTreeNodeChanged(Node treeNode)
  { fireTreeNodesChanged(new TreeModelEvent(this, getPathToRoot(treeNode)));
  }

//=============================================================================
//Tree-Manipulation-Support
//-----------------------------------------------------------------------------

/**
 * Adds a childNode to a parentNode.<br>
 * Notifies all listeners that have registered interest.
 *
 * @param parentNode as parent of the child Node.
 * @param childNode as Node to be added.
 */
  public void addXmlTreeNode(Node parentNode, Node childNode)
  { if (parentNode != null && childNode != null)
    { parentNode.appendChild(childNode);
      fireTreeNodeInserted (parentNode, childNode);
    }
  }

/**
 * Adds a node before a given successor.<br>
 * Notifies all listeners that have registered interest.
 *
 * @param node as Node to be added.
 * @param successor as Node after the to be added node
 */
  public void addXmlTreeNodeBefore(Node node, Node successor)
  { if (node != null)
    { Node parentNode = node.getParentNode();
      if (parentNode != null)
      { if (successor == null) // means add to the end
        { addXmlTreeNode(parentNode, node);
        }
        else // add before successor
        { parentNode.insertBefore(node, successor);
          fireTreeNodeInserted(parentNode, node);
        }
      }
    }
  }

/**
 * Removes a node from the tree.
 *
 * @param node as Node to be removed.
 */
  public void removeXmlTreeNode(Node node)
  { if (node != null)
    { Node parentNode = node.getParentNode();
      if (parentNode != null)
      { TreeModelEvent removeEvent = makeTreeModelChangeEvent(parentNode, node);
        parentNode.removeChild(node);
        fireTreeNodesRemoved(removeEvent);
      }
    }
  }
}