/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 * Created on March 25 2003
 */
package org.jboss.cache;


import org.apache.log4j.Logger;
import org.jgroups.View;

import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.tree.*;
import javax.transaction.TransactionManager;
import javax.transaction.Transaction;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.*;

//import java.util.List;

/**
 * Graphical view of a ReplicatedTree (using the MVC paradigm). An instance of this class needs to be given a
 * reference to the underlying model (ReplicatedTree) and needs to registers as a ReplicatedTreeListener. Changes
 * to the tree structure are propagated from the model to the view (via ReplicatedTreeListener), changes from the
 * GUI (e.g. by a user) are executed on the tree model (which will broadcast the changes to all replicas).<p>
 * The view itself caches only the nodes, but doesn't cache any of the data (HashMap) associated with it. When
 * data needs to be displayed, the underlying tree will be accessed directly.
 *
 * @version $Revision: 1.2.2.5 $
 */
public class TreeCacheView2 {
   TreeCacheGui2 gui_=null;
   TreeCache cache_=null;
   static Logger logger_=Logger.getLogger(TreeCacheView2.class.getName());

   public TreeCacheView2(TreeCache cache) throws Exception {
      this.cache_=cache;
   }

   public void start() throws Exception {
      if(gui_ == null) {
         logger_.info("start(): creating the GUI");
         gui_=new TreeCacheGui2(cache_);
      }
   }

   public void stop() {
      if(gui_ != null) {
         logger_.info("stop(): disposing the GUI");
         gui_.stopGui();
         gui_=null;
      }
   }

   void populateTree(String dir) throws Exception {
      File file=new File(dir);

      if(!file.exists()) return;

      put(dir, null);

      if(file.isDirectory()) {
         String[] children=file.list();
         if(children != null && children.length > 0) {
            for(int i=0; i < children.length; i++)
               populateTree(dir + "/" + children[i]);
         }
      }
   }

   void put(String fqn, Map m) {
      try {
         cache_.put(fqn, m);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheView2.put(): " + t);
      }
   }

   public static void main(String args[]) {
      TreeCache tree=null;
      TreeCacheView2 demo;
      String start_directory=null;
      String resource="META-INF/replSync-service.xml";

      for(int i=0; i < args.length; i++) {
         if(args[i].equals("-config")) {
            resource=args[++i];
            continue;
         }
         help();
         return;
      }

      try {
         tree=new TreeCache();
         PropertyConfigurator config=new PropertyConfigurator();
         config.configure(tree, resource);

         tree.addTreeCacheListener(new TreeCache.MyListener());
         tree.start();

         Runtime.getRuntime().addShutdownHook(new ShutdownThread(tree));

         demo=new TreeCacheView2(tree);
         demo.start();
         if(start_directory != null && start_directory.length() > 0) {
            demo.populateTree(start_directory);
         }
      }
      catch(Exception ex) {
         ex.printStackTrace();
      }
   }

   static class ShutdownThread extends Thread {
      TreeCache tree=null;

      ShutdownThread(TreeCache tree) {
         this.tree=tree;
      }

      public void run() {
         tree.stopService();
      }
   }

   static void help() {
      System.out.println("TreeCacheView [-help] " +
              "[-mbean_name <name of TreeCache MBean>] " +
              "[-start_directory <dirname>] [-props <props>] " +
              "[-use_queue <true/false>] [-queue_interval <ms>] " +
              "[-queue_max_elements <num>]");
   }

}


class TreeCacheGui2 extends JFrame implements WindowListener, TreeCacheListener,
        TreeSelectionListener, TableModelListener {
   TreeCache cache_;
   DefaultTreeModel tree_model=null;
   JTree jtree=null;
   DefaultTableModel table_model=new DefaultTableModel();
   JTable table=new JTable(table_model);
   MyNode root=new MyNode(SEP);
   String props=null;
   String selected_node=null;
   JPanel tablePanel=null;
   JMenu operationsMenu=null;
   JPopupMenu operationsPopup=null;
   JMenuBar menubar=null;
   boolean use_system_exit=false;
   static String SEP=TreeCache.SEPARATOR;
   private static final int KEY_COL_WIDTH=20;
   private static final int VAL_COL_WIDTH=300;
   final String STRING=String.class.getName();
   final String MAP=Map.class.getName();
   final String OBJECT=Object.class.getName();
   TransactionManager tx_mgr=null;
   Transaction tx=null;


   public TreeCacheGui2(TreeCache cache) throws Exception {
      this.cache_=cache;

      cache_.addTreeCacheListener(this);
      addNotify();
      setTitle("TreeCacheGui2: mbr=" + getLocalAddress());

      tree_model=new DefaultTreeModel(root);
      jtree=new JTree(tree_model);
      jtree.setDoubleBuffered(true);
      jtree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

      JScrollPane scroll_pane=new JScrollPane(jtree);

      populateTree();

      getContentPane().add(scroll_pane, BorderLayout.CENTER);
      addWindowListener(this);

      table_model.setColumnIdentifiers(new String[]{"Name", "Value"});
      table_model.addTableModelListener(this);

      setTableColumnWidths();

      tablePanel=new JPanel();
      tablePanel.setLayout(new BorderLayout());
      tablePanel.add(table.getTableHeader(), BorderLayout.NORTH);
      tablePanel.add(table, BorderLayout.CENTER);

      getContentPane().add(tablePanel, BorderLayout.SOUTH);

      jtree.addTreeSelectionListener(this);//REVISIT

      MouseListener ml=new MouseAdapter() {
         public void mouseClicked(MouseEvent e) {
            int selRow=jtree.getRowForLocation(e.getX(), e.getY());
            TreePath selPath=jtree.getPathForLocation(e.getX(), e.getY());
            if(selRow != -1) {
               selected_node=makeFQN(selPath.getPath());
               jtree.setSelectionPath(selPath);

               if(e.getModifiers() == java.awt.event.InputEvent.BUTTON3_MASK) {
                  operationsPopup.show(e.getComponent(),
                          e.getX(), e.getY());
               }
            }
         }
      };

      jtree.addMouseListener(ml);

      createMenus();
      setLocation(50, 50);
      setSize(getInsets().left + getInsets().right + 485,
              getInsets().top + getInsets().bottom + 367);

      init();
      setVisible(true);
      tx_mgr=cache_.getTransactionManager();
   }

   void setSystemExit(boolean flag) {
      use_system_exit=flag;
   }

   public void windowClosed(WindowEvent event) {
   }

   public void windowDeiconified(WindowEvent event) {
   }

   public void windowIconified(WindowEvent event) {
   }

   public void windowActivated(WindowEvent event) {
   }

   public void windowDeactivated(WindowEvent event) {
   }

   public void windowOpened(WindowEvent event) {
   }

   public void windowClosing(WindowEvent event) {
      stopGui();
   }


   public void tableChanged(TableModelEvent evt) {
      int row, col;
      String key, val;

      if(evt.getType() == TableModelEvent.UPDATE) {
         row=evt.getFirstRow();
         col=evt.getColumn();
         if(col == 0) {  // set()
            key=(String)table_model.getValueAt(row, col);
            val=(String)table_model.getValueAt(row, col + 1);
            if(key != null && val != null) {
               // tree.put(selected_node, key, val);

               try {
                  cache_.put(selected_node, key, val);
               }
               catch(Exception e) {
                  e.printStackTrace();
               }

            }
         }
         else {          // add()
            key=(String)table_model.getValueAt(row, col - 1);
            val=(String)table.getValueAt(row, col);
            if(key != null && val != null) {
               put(selected_node, key, val);
            }
         }
      }
   }


   public void valueChanged(TreeSelectionEvent evt) {
      TreePath path=evt.getPath();
      String fqn=SEP;
      String component_name;
      Map data=null;

      for(int i=0; i < path.getPathCount(); i++) {
         component_name=((MyNode)path.getPathComponent(i)).name;
         if(component_name.equals(SEP))
            continue;
         if(fqn.equals(SEP))
            fqn+=component_name;
         else
            fqn=fqn + SEP + component_name;
      }
      data=getData(fqn);
      if(data != null) {
         getContentPane().add(tablePanel, BorderLayout.SOUTH);
         populateTable(data);
         validate();
      }
      else {
         clearTable();
         getContentPane().remove(tablePanel);
         validate();
      }
   }



   /* ------------------ ReplicatedTree.ReplicatedTreeListener interface ------------ */

   public void nodeCreated(Fqn fqn) {
      MyNode n, p;

      n=root.add(fqn.toString());
      if(n != null) {
         p=(MyNode)n.getParent();
         tree_model.reload(p);
         jtree.scrollPathToVisible(new TreePath(n.getPath()));
      }
   }

   public void nodeRemoved(Fqn fqn) {
      MyNode n;
      TreeNode par;

      n=root.findNode(fqn.toString());
      if(n != null) {
         n.removeAllChildren();
         par=n.getParent();
         n.removeFromParent();
         tree_model.reload(par);
      }
   }


   public void nodeLoaded(Fqn fqn) {
      nodeCreated(fqn);
   }

   public void nodeEvicted(Fqn fqn) {
      nodeRemoved(fqn);
   }

   public void nodeModified(Fqn fqn) {
      // Map data;
      //data=getData(fqn);
      //populateTable(data); REVISIT
      /*
        poulateTable is the current table being shown is the info of the node. that is modified.
      */
   }

   public void nodeVisited(Fqn fqn)
   {
      //To change body of implemented methods use File | Settings | File Templates.
   }

   public void cacheStarted(TreeCache cache)
   {
      //To change body of implemented methods use File | Settings | File Templates.
   }

   public void cacheStopped(TreeCache cache)
   {
      //To change body of implemented methods use File | Settings | File Templates.
   }

   public void viewChange(final View new_view) {
      new Thread() {
         public void run() {
            Vector mbrship;
            if(new_view != null && (mbrship=new_view.getMembers()) != null) {
               _put(SEP, "members", mbrship);
               _put(SEP, "coordinator", mbrship.firstElement());
            }
         }
      }.start();
   }




   /* ---------------- End of ReplicatedTree.ReplicatedTreeListener interface -------- */

   /*----------------- Runnable implementation to make View change calles in AWT Thread ---*/

   public void run() {

   }



   /* ----------------------------- Private Methods ---------------------------------- */

   /**
    * Fetches all data from underlying tree model and display it graphically
    */
   void init() {
      Vector mbrship=null;

      // addGuiNode(SEP);
      mbrship=getMembers() != null ? (Vector)getMembers().clone() : null;
      if(mbrship != null && mbrship.size() > 0) {
         _put(SEP, "members", mbrship);
         _put(SEP, "coordinator", mbrship.firstElement());
      }
   }


   /**
    * Fetches all data from underlying tree model and display it graphically
    */
   private void populateTree() {
      addGuiNode(SEP);
   }


   /**
    * Recursively adds GUI nodes starting from fqn
    */
   void addGuiNode(String fqn) {
      Set children;
      String child_name;

      if(fqn == null) return;

      // 1 . Add myself
      root.add(fqn);

      // 2. Then add my children
      children=getChildrenNames(fqn);
      if(children != null) {
         for(Iterator it=children.iterator(); it.hasNext();) {
            child_name=(String)it.next();
            addGuiNode(fqn + SEP + child_name);
         }
      }
   }


   String makeFQN(Object[] path) {
      StringBuffer sb=new StringBuffer("");
      String tmp_name;

      if(path == null) return null;
      for(int i=0; i < path.length; i++) {
         tmp_name=((MyNode)path[i]).name;
         if(tmp_name.equals(SEP))
            continue;
         else
            sb.append(SEP + tmp_name);
      }
      tmp_name=sb.toString();
      if(tmp_name.length() == 0)
         return SEP;
      else
         return tmp_name;
   }

   void clearTable() {
      int num_rows=table.getRowCount();

      if(num_rows > 0) {
         for(int i=0; i < num_rows; i++)
            table_model.removeRow(0);
         table_model.fireTableRowsDeleted(0, num_rows - 1);
         repaint();
      }
   }


   void populateTable(Map data) {
      String key, strval="<null>";
      Object val;
      int num_rows=0;
      Map.Entry entry;

      if(data == null) return;
      num_rows=data.size();
      clearTable();

      if(num_rows > 0) {
         for(Iterator it=data.entrySet().iterator(); it.hasNext();) {
            entry=(Map.Entry)it.next();
            key=(String)entry.getKey();
            val=entry.getValue();
            if(val != null) strval=val.toString();
            table_model.addRow(new Object[]{key, strval});
         }
         table_model.fireTableRowsInserted(0, num_rows - 1);
         validate();
      }
   }

   private void setTableColumnWidths() {
      table.sizeColumnsToFit(JTable.AUTO_RESIZE_NEXT_COLUMN);
      TableColumn column=null;
      column=table.getColumnModel().getColumn(0);
      column.setMinWidth(KEY_COL_WIDTH);
      column.setPreferredWidth(KEY_COL_WIDTH);
      column=table.getColumnModel().getColumn(1);
      column.setPreferredWidth(VAL_COL_WIDTH);
   }

   private void createMenus() {
      menubar=new JMenuBar();
      operationsMenu=new JMenu("Operations");
      AddNodeAction addNode=new AddNodeAction();
      addNode.putValue(AbstractAction.NAME, "Add to this node");
      LoadAction load_action=new LoadAction();
      load_action.putValue(AbstractAction.NAME, "Load from the CacheLoader");
      RemoveNodeAction removeNode=new RemoveNodeAction();
      removeNode.putValue(AbstractAction.NAME, "Remove this node");
      EvictAction evict_action=new EvictAction();
      evict_action.putValue(AbstractAction.NAME, "Evict from the Cache");
      AddModifyDataForNodeAction addModAction=new AddModifyDataForNodeAction();
      addModAction.putValue(AbstractAction.NAME, "Add/Modify data");
      PrintLockInfoAction print_locks=new PrintLockInfoAction();
      print_locks.putValue(AbstractAction.NAME, "Print lock information (stdout)");
      ReleaseAllLocksAction release_locks=new ReleaseAllLocksAction();
      release_locks.putValue(AbstractAction.NAME, "Release all locks");
      ExitAction exitAction=new ExitAction();
      exitAction.putValue(AbstractAction.NAME, "Exit");
      StartTransaction start_tx=new StartTransaction();
      start_tx.putValue(AbstractAction.NAME, "Start TX");
      CommitTransaction commit_tx=new CommitTransaction();
      commit_tx.putValue(AbstractAction.NAME, "Commit TX");
      RollbackTransaction rollback_tx=new RollbackTransaction();
      rollback_tx.putValue(AbstractAction.NAME, "Rollback TX");
      operationsMenu.add(addNode);
      operationsMenu.add(load_action);
      operationsMenu.add(removeNode);
      operationsMenu.add(evict_action);
      operationsMenu.add(addModAction);
      operationsMenu.add(print_locks);
      operationsMenu.add(release_locks);
      operationsMenu.add(start_tx);
      operationsMenu.add(commit_tx);
      operationsMenu.add(rollback_tx);
      operationsMenu.add(exitAction);
      menubar.add(operationsMenu);
      setJMenuBar(menubar);

      operationsPopup=new JPopupMenu();
      operationsPopup.add(addNode);
      operationsPopup.add(load_action);
      operationsPopup.add(evict_action);
      operationsPopup.add(removeNode);
      operationsPopup.add(addModAction);
   }

   Object getLocalAddress() {
      try {
         return cache_.getLocalAddress();
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.getLocalAddress(): " + t);
         return null;
      }
   }

   Map getData(String fqn) {
      Map data;
      Set keys;
      String key;
      Object value;

      if(fqn == null) return null;
      keys=getKeys(fqn);
      if(keys == null) return null;
      data=new HashMap();
      for(Iterator it=keys.iterator(); it.hasNext();) {
         key=(String)it.next();
         value=get(fqn, key);
         if(value != null)
            data.put(key, value);
      }
      return data;
   }


   void load(String fqn) {
      try {
         cache_.load(fqn);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.load(): " + t);
      }
   }

   void evict(String fqn) {
      try {
         cache_.evict(Fqn.fromString(fqn));
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.evict(): " + t);
      }
   }

   void put(String fqn, Map m) {
      try {
         cache_.put(fqn, m);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.put(): " + t);
      }
   }

   void _put(String fqn, Map m) {
      try {
         cache_._put(null, fqn, m, false);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2._put(): " + t);
      }
   }

   void put(String fqn, String key, Object value) {
      try {
         cache_.put(fqn, key, value);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.put(): " + t);
      }
   }

   void _put(String fqn, String key, Object value) {
      try {
         cache_._put(null, fqn, key, value, false);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2._put(): " + t);
      }
   }

   Set getKeys(String fqn) {
      try {
         return cache_.getKeys(fqn);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.getKeys(): " + t);
         return null;
      }
   }

   Object get(String fqn, String key) {
      try {
         return cache_.get(fqn, key);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.get(): " + t);
         return null;
      }
   }

   Set getChildrenNames(String fqn) {
      try {
         return cache_.getChildrenNames(fqn);
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui2.getChildrenNames(): " + t);
         return null;
      }
   }

   Vector getMembers() {
      try {
         return cache_.getMembers();
      }
      catch(Throwable t) {
         System.err.println("TreeCacheGui.getMembers(): " + t);
         return null;
      }
   }


   /* -------------------------- End of Private Methods ------------------------------ */

   /*----------------------- Actions ---------------------------*/
   class ExitAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         stopGui();
      }
   }


   void stopGui() {
      if(cache_ != null) {
         try {
            cache_.stopService();
            cache_.destroyService();
            cache_=null;
         }
         catch(Throwable t) {
            t.printStackTrace();
         }
      }
      dispose();
      System.exit(0);
   }

   class AddNodeAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         JTextField fqnTextField=new JTextField();
         if(selected_node != null)
            fqnTextField.setText(selected_node);
         Object[] information={"Enter fully qualified name",
                               fqnTextField};
         final String btnString1="OK";
         final String btnString2="Cancel";
         Object[] options={btnString1, btnString2};
         int userChoice=JOptionPane.showOptionDialog(null,
                 information,
                 "Add Node",
                 JOptionPane.YES_NO_OPTION,
                 JOptionPane.PLAIN_MESSAGE,
                 null,
                 options,
                 options[0]);
         if(userChoice == 0) {
            String userInput=fqnTextField.getText();
            put(userInput, null);
         }
      }
   }

   class LoadAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         JTextField fqnTextField=new JTextField();
         if(selected_node != null)
            fqnTextField.setText(selected_node);
         Object[] information={"Enter fully qualified name",
                               fqnTextField};
         final String btnString1="OK";
         final String btnString2="Cancel";
         Object[] options={btnString1, btnString2};
         int userChoice=JOptionPane.showOptionDialog(null,
                 information,
                 "Load Node",
                 JOptionPane.YES_NO_OPTION,
                 JOptionPane.PLAIN_MESSAGE,
                 null,
                 options,
                 options[0]);
         if(userChoice == 0) {
            String userInput=fqnTextField.getText();
            load(userInput);
         }
      }
   }

   class EvictAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         JTextField fqnTextField=new JTextField();
         if(selected_node != null)
            fqnTextField.setText(selected_node);
         Object[] information={"Enter fully qualified name",
                               fqnTextField};
         final String btnString1="OK";
         final String btnString2="Cancel";
         Object[] options={btnString1, btnString2};
         int userChoice=JOptionPane.showOptionDialog(null,
                 information,
                 "Evict Node",
                 JOptionPane.YES_NO_OPTION,
                 JOptionPane.PLAIN_MESSAGE,
                 null,
                 options,
                 options[0]);
         if(userChoice == 0) {
            String userInput=fqnTextField.getText();
            evict(userInput);
         }
      }
   }


   class StartTransaction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         if(tx_mgr == null) {
            System.err.println("no TransactionManager specified");
            return;
         }
         if(tx != null) {
            System.err.println("transaction is already running: " + tx);
            return;
         }
         try {
            tx_mgr.begin();
            tx=tx_mgr.getTransaction();
         }
         catch(Throwable t) {
            t.printStackTrace();
         }
      }
   }

   class CommitTransaction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         if(tx == null) {
            System.err.println("transaction is not running");
            return;
         }
         try {
            tx.commit();
         }
         catch(Throwable t) {
            t.printStackTrace();
         }
         finally {
            tx=null;
         }
      }
   }

   class RollbackTransaction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         if(tx == null) {
            System.err.println("transaction is not running");
            return;
         }
         try {
            tx.rollback();
         }
         catch(Throwable t) {
            t.printStackTrace();
         }
         finally {
            tx=null;
         }
      }
   }

   class PrintLockInfoAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         System.out.println("\n*** lock information ****\n" + cache_.printLockInfo());
      }
   }

   class ReleaseAllLocksAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         cache_.releaseAllLocks("/");
      }
   }

   class RemoveNodeAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         try {
            cache_.remove(selected_node);
         }
         catch(Throwable t) {
            System.err.println("RemoveNodeAction.actionPerformed(): " + t);
         }
      }
   }

   class AddModifyDataForNodeAction extends AbstractAction {
      public void actionPerformed(ActionEvent e) {
         Map data=getData(selected_node);
         if(data != null && data.size() > 0) {
         }
         else {
            clearTable();
            data=new HashMap();
            data.put("Add Key", "Add Value");

         }
         populateTable(data);
         getContentPane().add(tablePanel, BorderLayout.SOUTH);
         validate();

      }
   }


   class MyNode extends DefaultMutableTreeNode {
      String name="<unnamed>";


      MyNode(String name) {
         this.name=name;
      }


      /**
       * Adds a new node to the view. Intermediary nodes will be created if they don't yet exist.
       * Returns the first node that was created or null if node already existed
       */
      public MyNode add(String fqn) {
         MyNode curr, n, ret=null;
         StringTokenizer tok;
         String child_name;

         if(fqn == null) return null;
         curr=this;
         tok=new StringTokenizer(fqn, TreeCacheGui2.SEP);

         while(tok.hasMoreTokens()) {
            child_name=tok.nextToken();
            n=curr.findChild(child_name);
            if(n == null) {
               n=new MyNode(child_name);
               if(ret == null) ret=n;
               curr.add(n);
            }
            curr=n;
         }
         return ret;
      }


      /**
       * Removes a node from the view. Child nodes will be removed as well
       */
      public void remove(String fqn) {
         removeFromParent();
      }


      MyNode findNode(String fqn) {
         MyNode curr, n;
         StringTokenizer tok;
         String child_name;

         if(fqn == null) return null;
         curr=this;
         tok=new StringTokenizer(fqn, TreeCacheGui2.SEP);

         while(tok.hasMoreTokens()) {
            child_name=tok.nextToken();
            n=curr.findChild(child_name);
            if(n == null)
               return null;
            curr=n;
         }
         return curr;
      }


      MyNode findChild(String relative_name) {
         MyNode child;

         if(relative_name == null || getChildCount() == 0)
            return null;
         for(int i=0; i < getChildCount(); i++) {
            child=(MyNode)getChildAt(i);
            if(child.name == null) {
               continue;
            }

            if(child.name.equals(relative_name))
               return child;
         }
         return null;
      }


      String print(int indent) {
         StringBuffer sb=new StringBuffer();

         for(int i=0; i < indent; i++)
            sb.append(" ");
         if(!isRoot()) {
            if(name == null)
               sb.append("/<unnamed>");
            else {
               sb.append(TreeCacheGui2.SEP + name);
            }
         }
         sb.append("\n");
         if(getChildCount() > 0) {
            if(isRoot())
               indent=0;
            else
               indent+=4;
            for(int i=0; i < getChildCount(); i++)
               sb.append(((MyNode)getChildAt(i)).print(indent));
         }
         return sb.toString();
      }


      public String toString() {
         return name;
      }

   }


}












