package org.apache.ecs.examples;

import java.io.*;
import java.util.*;
import javax.swing.tree.*;
import org.apache.ecs.xml.*;
import org.apache.ecs.html.*;

/**
 * This JavaBean returns the hierarchical structure described in a
 * javax.swing.tree.DefaultMutableTreeNode as valid XHTML. This class
 * is a very simple counterpart of the javax.swing.JTree with the
 * exception that the external Controller is integrated into this View.
 * If you want your tree elements (nodes and leafs) to be marked with an
 * anchor, you'll have to make sure that the name of your node contains
 * such an anchor.
 *
 * <p>You can use this class in a JavaServerPage like this:</p>
 *
 * <p><blockquote>
 *    <p>...template text...</p>
 *    <p><code>
 *      &lt;%-- Get the bean from the request scope or
 *      create a new one if none exits --%&gt;<br />
 *      &lt;jsp:useBean id="tree" scope="request"
 *      class="org.apache.ecs.examples.HtmlTree"&gt;
 *      <blockquote>
 *        <p>&lt;%-- Read the path from the request --%&gt;<br />
 *        &lt;jsp:setProperty name="tree"
 *        property="path" param="path" /&gt;</p>
 *        <p>&lt;%-- Set bean properties --%&gt;<br />
 *        &lt;jsp:setProperty name="tree"
 *        property="rootVisible" value="true" /&gt;<br />
 *        &lt;jsp:setProperty name="tree"
 *        property="indentation" value="2" /&gt;<br />
 *        &lt;jsp:setProperty name="tree"
 *        property="openIcon" value="open.gif" /&gt;<br />
 *        &lt;jsp:setProperty name="tree"
 *        property="closedIcon" value="closed.gif" /&gt;<br />
 *        &lt;jsp:setProperty name="tree"
 *        property="leafIcon" value="leaf.gif" /&gt;</p>
 *        <p>&lt;%-- Workaround for Netscape
 *        and Opera browsers --%&gt;<br />
 *        &lt;jsp:setProperty name="tree"
 *        property="action" value="tree.jsp" /&gt;</p>
 *      </blockquote>
 *      &lt;/jsp:useBean&gt;
 *    </code></p>
 *    <p>...template text...</p>
 *    <p><code>
 *      &lt;%-- Get the XHTML output from the bean --%&gt;<br />
 *      &lt;jsp:getProperty name="tree" property="html" /&gt;
 *    </code></p>
 * </blockquote></p>
 *
 * Although this class is just an example of using parts of the Element
 * Construction Set, you can use it quite well in real web applications.
 * This example is designed after a Tree seen at <a href=
 * "http://sourceforge.net">SourceForge</a>.
 *
 * @version 1.0, 2001/10/30
 * @author <a href="mailto:horombo@gmx.de">Christian Brensing</a>
 */
public class HtmlTree implements Serializable {

  /**
   * Number of blank spaces around each hierarchical level
   * will be indented in relation to its parent node.
   * Default is 2.
   */
  protected int indentation = 2;

  /**
   * Path to the requested node to be displayed,
   * described as colon separated integer values.
   * Default is an empty String.
   */
  protected String path = new String();

  /**
   * Parameter name used to build the query string for the
   * anchor that acts as the Controller for opening and
   * closing nodes. Default is &quot;path&quot;.
   */
  protected String parameterName = new String("path");

  /**
   * Array that stores the index of each node
   * read from the path property.
   */
  private int[] pathArray = new int[0];

  /**
   * Icon for displaying leafs.
   * Default is an empty String.
   */
  protected String leafIcon = new String();

  /**
   * Icon for displaying open nodes.
   * Default is an empty String.
   */
  protected String openIcon = new String();

  /**
   * Icon for displaying closed nodes.
   * Default is an empty String.
   */
  protected String closedIcon = new String();

  /**
   * Context path of a web resource for the workaround
   * of a strange behaviour of Netscape and Opera
   * browsers. Default is an empty String.
   */
  protected String action = new String();

  /**
   * True if the root node is displayed, false if
   * its children are the highest visible nodes.
   * Default is true.
   */
  protected boolean rootVisible = true;

  /**
   * The node that defines the tree displayed by this object.
   * Default is a sample model.
   */
  protected DefaultMutableTreeNode root = getDemoModel();

  /**
   * Stores the requested node to be displayed.
   * Default is the root node.
   */
  private DefaultMutableTreeNode displayNode = root;

  /**
   * Returns a HtmlTree with a sample model.
   */
  public HtmlTree() {
  } // Constructor

  /**
   * Returns a HtmlTree with the specified DefaultMutableTreeNode
   * as its root. If the DefaultMutableTreeNode is <tt>null</tt>,
   * a sample model is set automatically.
   *
   * @param root a DefaultMutableTreeNode object
   */
  public HtmlTree(DefaultMutableTreeNode root) {

    setRoot(root);

  } // Constructor

  /**
   * Sets the root node that will provide the data. If the
   * specified DefaultMutableTreeNode is <tt>null</tt>,
   * a sample model is set automatically.
   *
   * @param root a DefaultMutableTreeNode object
   */
  public void setRoot(DefaultMutableTreeNode root) {

    // Check property
    if (root == null) { root = getDemoModel(); }

    // Set property
    this.root = root;

  } // setRoot

  /**
   * Sets the path - described as colon separated integer values -
   * to the requested node to be displayed.
   *
   * @param path the path to the requested node to be displayed
   */
  public void setPath(String path) {

    // Check property
    if (path == null) { path = new String(); }

    // Set property
    this.path = path;

    // Tokenize the path
    StringTokenizer st = new StringTokenizer(path,":");

    // Reset path array
    pathArray = new int[st.countTokens()];

    // Save value of each token in the array
    if (st.countTokens() > 0) {
      for (int i = 0; i <= st.countTokens(); i++) {
        pathArray[i] = Integer.
        parseInt(st.nextToken().trim());
      } // next i
    } // end if

  } // setPath

  /**
   * Returns a path constructed from the path array for the
   * specified level.
   *
   * @param level the distance from the node to the root node.
   * @return the path for the specified level
   */
  private String getPath(int level) {

    // New StringBuffer for the path generation
    StringBuffer autoPath = new StringBuffer();

    // For each node
    for (int i = 0; i < level; i++) {

      // Add node index (read from the path array)
      autoPath.append(pathArray[i]);

      // Add path separator (colon)
      if (i < (level - 1)) { autoPath.append(":"); }

    } // next i

    // Return generated path
    return autoPath.toString();

  } // getPath

  /**
   * Sets the name of the parameter used to build the query
   * string for the anchor that acts as the Controller for
   * opening and closing nodes. You only have to set this
   * name when the default name &quot;path&quot; is already
   * used by your web application.
   *
   * @param parameterName the name of the parameter used to
   * build the query string
   */
  public void setParameterName(String parameterName) {

    // Check property
    if (parameterName == null ||
    parameterName.equals("")) {
    parameterName = new String("path"); }

    // Set property
    this.parameterName = parameterName;

  } // setParameterName

  /**
   * Returns the name of the parameter used to build the
   * query string for the anchor that acts as the Controller
   * for opening and closing nodes.
   *
   * @return the name of the parameter
   */
  public String getParameterName() {

    return parameterName;

  } // getParameterName

  /**
   * Determines whether or not the root node is visible.
   *
   * @param rootVisible true if the root node of the tree
   * is to be displayed
   */
  public void setRootVisible(boolean rootVisible) {

    this.rootVisible = rootVisible;

  } // setRootVisible

  /**
   * Returns true if the root node of the tree is displayed.
   *
   * @return true if the root node of the tree is displayed
   */
  public boolean isRootVisible() {

    return rootVisible;

  } // isRootVisible

  /**
   * Sets the number of blank spaces around each hierarchical
   * level will be indented in relation to its parent node.
   *
   * @param indentation the number of blank spaces
   */
  public void setIndentation(int indentation) {

    this.indentation = indentation;

  } // setIndentation

  /**
   * Sets the icon for displaying open nodes.
   *
   * @param openIcon the URI of an image file
   */
  public void setOpenIcon(String openIcon) {

    this.openIcon = openIcon;

  } // setOpenIcon

  /**
   * Sets the icon for displaying closed nodes.
   *
   * @param closedIcon the URI of an image file
   */
  public void setClosedIcon(String closedIcon) {

    this.closedIcon = closedIcon;

  } // setClosedIcon

  /**
   * Sets the icon for displaying leafs.
   *
   * @param leafIcon the URI of an image file
   */
  public void setLeafIcon(String leafIcon) {

    this.leafIcon = leafIcon;

  } // setLeafIcon

  /**
   * Workaround of a strange behaviour of Netscape and Opera browsers.
   * In these browsers the relative URL containg only the query string
   * with the path information (e.g. &quot;?path=0:1&quot;) are not
   * translated correctly into an absoulte URL. By setting a context
   * path, on which to append the query string, this behaviour should
   * be fixed.
   *
   * @param action the context path of a web resource (e.g. a JSP-File)
   */
  public void setAction(String action) {

    this.action = action;

  } // setAction

  /**
   * Returns the name of the currently expanded node to
   * be displayed.
   *
   * @return the name of the node to be displayed
   */
  public String getNodeName() {

    return expand().toString();

  } // getNodeName

  /**
   * Constructs a valid XHTML &lt;img&gt;-tag from the specified icon.
   * Although this method uses the xml-package to generate a valid tag,
   * the xhtml-package would provide the same functionality.
   *
   * @param icon the URI of an image file
   * @return a valid XHTML &lt;img&gt;-tag
   */
  private String getImg(String icon) {

    // New <img>
    XML img = new XML("img",false);

    // Check specified icon property
    if (icon == null) { icon = new String(); }

    // Set src attribute
    img.addAttribute("src",icon);

    // return <img>
    return img.toString();

  } // getImg

  /**
   * Expands the tree by following the specified path
   * and returns the requested node to be displayed
   *
   * @return the requested node to be displayed
   */
  private DefaultMutableTreeNode expand() {

    // Reset node to be displayed
    displayNode = root;

    // Iterate trough the path array
    for (int i = 0; i < pathArray.length; i++) {
      displayNode = (DefaultMutableTreeNode)
      displayNode.getChildAt(pathArray[i]);
    } // next i

    // Return node to be displayed
    return displayNode;

  } // expand

  /**
   * Returns the hierarchical structure described in the specified root
   * node as valid XHTML. For perfomance reasons all parent nodes of the
   * requested node are collapsed. The source code does not contain any
   * formatting tags or attributes. I recommend that you use Cascading
   * Style Sheets instead. For this purpose, the tree elements are marked
   * with the class attribute values &quot;tree&quot;, &quot;parent&quot;
   * and &quot;child&quot;.
   *
   * @return the hierarchical structure within a &lt;table&gt;-tag
   */
  public String getHtml() {

    // Expand the tree
    displayNode = expand();

    // New <table>
    Table table = new Table();
    table.setClass("tree");

    // Reset auto indentation
    int autoIndentation = 0;

    // Initialize ancestor node with the first
    // child of the node to be displayed
    TreeNode ancestor = displayNode.getFirstChild();

    // Read all ancestors into an array
    ArrayList list = new ArrayList();
    while ((ancestor = ancestor.getParent())
    != null) { list.add(ancestor); }

    // For each ancestor (ordered desc beginning at the root)
    for (int index = list.size(); index > 0; index--) {

      // Get current ancestor
      DefaultMutableTreeNode parent =
      (DefaultMutableTreeNode)list.
      get(index-1);

      // Displays the ancestor if it's not the root
      // or the isRootVisible property is set to true
      if (!parent.isRoot() || rootVisible) {

        // New <td>
        TD td = new TD();
        td.setClass("parent");

        // Generate href for this ancestor (the
        // level is decreased by one to provide
        // an anchor to its parent node)
        String href = action.concat("?").
        concat(parameterName).concat("=").
        concat(getPath(parent.getLevel()-1));

        // Add indentation to <td>
        for (int i = 0; i < autoIndentation; i++)
        { td.addElement("&nbsp;"); }

        // Add icon with <a> to <td>
        td.addElement(new A(href,getImg(openIcon)));

        // Add ancestor with <nobr> to <td>
        td.addElement(new NOBR(parent.toString()));

        // Add <td> to <tr> to <table>
        table.addElement(new TR(td));

        // Increment auto indentation
        autoIndentation = autoIndentation + indentation;

      } // end if rootVisible

    } // next index

    // For each child
    for (int index = 0; index < displayNode.getChildCount(); index++) {

      // New <td>
      TD td = new TD();
      td.setClass("child");

      // Get current child
      DefaultMutableTreeNode child =
      (DefaultMutableTreeNode)displayNode.
      getChildAt(index);

      // Generate path == path + child index
      String autoPath = getPath(child.getLevel()-1).
      concat(":").concat(String.valueOf(index));

      // Generate href for this child
      String href = action.concat("?").
      concat(parameterName).concat("=").
      concat(autoPath);

      // Add indentation to <td>
      for (int i = 0; i < autoIndentation; i++)
      { td.addElement("&nbsp;"); }

      // New <img> with default leaf icon
      String img = getImg(leafIcon);

      // Set closed node icon, if child is not a leaf
      if (!child.isLeaf()) { img = getImg(closedIcon); }

      // Add icon with <a> (not for leafs) to <td>
      if (!child.isLeaf()) {td.addElement(new A(href,img)); }

      // Add icon without <a> (leafs only) to <td>
      if (child.isLeaf()) { td.addElement(img); }

      // Add child with <nobr> to <td>
      td.addElement(new NOBR(child.toString()));

      // Add <td> to <tr> to <table>
      table.addElement(new TR(td));

    } // next index

    // Return <table>
    return table.toString();

  } // getHtml

  /**
   * Returns a sample tree model.
   *
   * @return a DefaultMutableTreeNode with a sample tree model
   */
  private DefaultMutableTreeNode getDemoModel() {

    // New root node
    DefaultMutableTreeNode root =
    new DefaultMutableTreeNode("Root");

    // First level
    for (int i = 1; i <= 5; i++) {

      // New node
      DefaultMutableTreeNode child =
      new DefaultMutableTreeNode("Folder-" + i);

      // Add node to root
      root.add(child);

      // Second level
      for (int j = 1; j <= 3; j++) {

        // New node
        DefaultMutableTreeNode subchild =
        new DefaultMutableTreeNode("Subfolder-" + j);

        // Add node to parent node
        child.add(subchild);

        // Third level
        for (int k = 1; k <= 3; k++) {

          // New anchor
          A a = new A("http://jakarta.apache.org");
          a.setTarget("target").addElement("Document-" + k);

          // New node (leaf)
          DefaultMutableTreeNode document =
          new DefaultMutableTreeNode(a.toString());

          // Add node to parent node
          subchild.add(document);

        } // next k

      } // next j

    } // next i

    // Return root node
    return root;

  } // getDemoModel

} // HtmlTree
