/*
 * 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.aop;


import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JTree;
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.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jgroups.View;
import org.jboss.cache.TreeCache;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCacheListener;
import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCacheView;

import bsh.Interpreter;
import bsh.util.JConsole;


/**
 * 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: 2164 $
 */
public class TreeCacheView2 {
   static TreeCacheGui2 gui_=null;
   static boolean useConsole = false;
   TreeCache cache_=null;
   static Log log_=LogFactory.getLog(TreeCacheView2.class.getName());

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

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

   public void stop() {
      if(gui_ != null) {
         log_.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) {
         log_.error("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("-console")) {
				useConsole = true;
				continue;
			}
			if (args[i].equals("-config")) {
				resource = args[++i];
				continue;
			}
			help();
			return;
		}

		try {
			if (useConsole) {
				demo = new TreeCacheView2(null);
				demo.start();
			} else {
				tree = new TreeCache();
				PropertyConfigurator config = new PropertyConfigurator();
				config.configure(tree, resource);

				tree.addTreeCacheListener(new TreeCacheView.MyListener());
				tree.start();
				demo = new TreeCacheView2(tree);
				demo.start();
				if (start_directory != null && start_directory.length() > 0) {
					demo.populateTree(start_directory);
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
   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>] [-console]" +
              "[-queue_max_elements <num>]");
   }

}

class ShutdownThread extends Thread {
    TreeCache tree=null;

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

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

class TreeCacheGui2 extends JFrame implements WindowListener, TreeCacheListener,
        TreeSelectionListener, TableModelListener {
   private static final long serialVersionUID = -1242167331988194987L;

   TreeCache cache_;
   DefaultTreeModel tree_model=null;
   Log log=LogFactory.getLog(getClass());
   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= Fqn.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;
   JPanel mainPanel;


   public TreeCacheGui2(TreeCache cache) throws Exception {
		addNotify();

		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);
		mainPanel = new JPanel();
		mainPanel.setLayout(new BorderLayout());
		mainPanel.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);

		mainPanel.add(tablePanel, BorderLayout.SOUTH);
		JSplitPane contentPanel=null;
		if (TreeCacheView2.useConsole) {
			JConsole bshConsole = new JConsole();
			Interpreter interpreter = new Interpreter(bshConsole);
			interpreter.getNameSpace().importCommands("org.jboss.cache.util");
			interpreter.setShowResults(!interpreter.getShowResults()); // show();
			//System.setIn(bshConsole.getInputStream());
			System.setOut(bshConsole.getOut());
			System.setErr(bshConsole.getErr());
			Thread t = new Thread(interpreter);
			t.start();

			contentPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT,mainPanel, bshConsole);
			getContentPane().add(contentPanel);
		} else {
			getContentPane().add(mainPanel);
		}
		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();
		setCache(cache);
		setVisible(true);

		if(TreeCacheView2.useConsole)
		{
			//has to be called after setVisible() otherwise no effect
			contentPanel.setDividerLocation(0.65);
		}
	}

   public void setCache(TreeCache tree)
   {
	   cache_ = tree;
	   if(cache_!= null)
	   {
		   Runtime.getRuntime().addShutdownHook(new ShutdownThread(tree));
		   cache_.addTreeCacheListener(this);
		   setTitle("TreeCacheGui2: mbr=" + getLocalAddress());
		   tx_mgr=cache_.getTransactionManager();
		   populateTree();
	   }
	   else
	   {
		   setTitle("Cache undefined");
		   if(tree_model!=null)
		   {
			   root = new MyNode(SEP);
			   tree_model.setRoot(root);
			   tree_model.reload();

			}
		   if(table_model!=null)
		   {
			   clearTable();
		   }
	   }
   }

   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.fromString(fqn));
      if(data != null) {
         mainPanel.add(tablePanel, BorderLayout.SOUTH);
         populateTable(data);
         validate();
      }
      else {
         clearTable();
         mainPanel.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);
      /*
        poulateTable is the current table being shown is the info of the node. that is modified.
      */
      populateTable(data);
   }

   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=it.next().toString();
            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(IsPojoInternalNode(key)) continue;
            if(val != null) strval=val.toString();
            table_model.addRow(new Object[]{key, strval});
         }
         table_model.fireTableRowsInserted(0, num_rows - 1);
         validate();
      }
   }

   private boolean IsPojoInternalNode(String key)
   {
      if(key.equals(InternalDelegate.CLASS_INTERNAL)) return true;
      if(key.equals(AOPInstance.KEY)) return true;
      if(key.equals("_lock_")) return true;
      return false;
   }

   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) {
         log.error("TreeCacheGui2.getLocalAddress(): " + t);
         return null;
      }
   }

   Map getData(Fqn 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;
      */
      // Retreive it directly from internal cache bypassing the lock such there is no chance of deadlock.
      return cache_._getData(fqn);
   }


   void load(String fqn) {
      try {
         cache_.load(fqn);
      }
      catch(Throwable t) {
         log.error("TreeCacheGui2.load(): " + t);
      }
   }

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

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

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

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

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

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

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

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

   Vector getMembers() {
      try {
         return cache_.getMembers();
      }
      catch(Throwable t) {
         log.error("TreeCacheGui.getMembers(): " + t);
         return null;
      }
   }


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

   /*----------------------- Actions ---------------------------*/
   class ExitAction extends AbstractAction {
      private static final long serialVersionUID = -5364163916172148038L;
      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 {
      private static final long serialVersionUID = 7084928639244438800L;
      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 DataNode",
                 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 {
      private static final long serialVersionUID = -6998760732995584428L;
      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 DataNode",
                 JOptionPane.YES_NO_OPTION,
                 JOptionPane.PLAIN_MESSAGE,
                 null,
                 options,
                 options[0]);
         if(userChoice == 0) {
            String userInput=fqnTextField.getText();
            load(userInput);
         }
      }
   }

   class EvictAction extends AbstractAction {
      private static final long serialVersionUID = 6007500908549034215L;
      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 DataNode",
                 JOptionPane.YES_NO_OPTION,
                 JOptionPane.PLAIN_MESSAGE,
                 null,
                 options,
                 options[0]);
         if(userChoice == 0) {
            String userInput=fqnTextField.getText();
            evict(userInput);
         }
      }
   }


   class StartTransaction extends AbstractAction {
      private static final long serialVersionUID = 7059131008813144857L;
      public void actionPerformed(ActionEvent e) {
         if(tx_mgr == null) {
            log.error("no TransactionManager specified");
            return;
         }
         if(tx != null) {
            log.error("transaction is already running: " + tx);
            return;
         }
         try {
            tx_mgr.begin();
            tx=tx_mgr.getTransaction();
         }
         catch(Throwable t) {
            t.printStackTrace();
         }
      }
   }

   class CommitTransaction extends AbstractAction {
      private static final long serialVersionUID = 5426108920883879873L;
      public void actionPerformed(ActionEvent e) {
         if(tx == null) {
            log.error("transaction is not running");
            return;
         }
         try {
            tx.commit();
         }
         catch(Throwable t) {
            t.printStackTrace();
         }
         finally {
            tx=null;
         }
      }
   }

   class RollbackTransaction extends AbstractAction {
      private static final long serialVersionUID = -4836748411400541430L;
      public void actionPerformed(ActionEvent e) {
         if(tx == null) {
            log.error("transaction is not running");
            return;
         }
         try {
            tx.rollback();
         }
         catch(Throwable t) {
            t.printStackTrace();
         }
         finally {
            tx=null;
         }
      }
   }

   class PrintLockInfoAction extends AbstractAction {
      private static final long serialVersionUID = -2171307516592250436L;
      public void actionPerformed(ActionEvent e) {
         System.out.println("\n*** lock information ****\n" + cache_.printLockInfo());
      }
   }

   class ReleaseAllLocksAction extends AbstractAction {
      private static final long serialVersionUID = 6894888234400908985L;
      public void actionPerformed(ActionEvent e) {
         cache_.releaseAllLocks("/");
      }
   }

   class RemoveNodeAction extends AbstractAction {
      private static final long serialVersionUID = 3746013603940497991L;
      public void actionPerformed(ActionEvent e) {
         try {
            cache_.remove(selected_node);
         }
         catch(Throwable t) {
            log.error("RemoveNodeAction.actionPerformed(): " + t);
         }
      }
   }

   class AddModifyDataForNodeAction extends AbstractAction {
      private static final long serialVersionUID = -7656592171312920825L;
      public void actionPerformed(ActionEvent e) {
         Map data=getData(Fqn.fromString(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 {
      private static final long serialVersionUID = 4882445905140460053L;
      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;
      }

   }
}












