# DirectoryEdit.java

    /*
    ** Module   : DirectoryEdit.java
    ** Abstract : general purpose interactive directory editor
    **
    ** Copyright (c) 2005-2022, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
    ** 001 NVS 20050428   @21012 Created the initial version which can handle
    **                           editing attribute values and printing.
    ** 002 NVS 20050505   @21125 The default timeout was too short for LDAP
    **                           directory accesses. Raised it to 60 seconds.
    **                           Rewritten the top level menu. Now the bind()
    **                           operation is done right away and unbind() is
    **                           done when exiting the application.
    ** 003 NVS 20050518   @21228 Changed top menu to allow empty batch path,
    **                           which is the directory root.
    ** 004 SIY 20050520   @21265 Rewritten with different menu model, finished
    **                           remaining unimplemented items, added missing
    **                           imported methods, many cleanups and changes
    **                           in the formatting and comments.
    ** 005 NVS 20050527   @21315 Renamed to DirectoryEdit and promoted to the status of utility.
    ** 006 NVS 20050902   @22447 New directory based EIR registration puts
    **                           limitations on exported names. "directory"
    **                           group exports are renamed to comply.
    ** 007 NVS 20050914   @22706 DirectoryEdit is a TTY-based utility and as
    **                           such has to suspend CHARVA on entry.
    ** 008 NVS 20050920   @22758 Fixed empty command processing and CHARVA detection.
    ** 009 GES 20070111   @31803 Removed unnecessary use of notifiable. This
    **                           class is currently non-functional but is
    **                           being retained for future re-enablement.
    ** 010 GES 20070115   @31840 Match minor interface change in base class.
    ** 011 EVL 20070609   @34005 Adding explicit import of the class
    **                           com.goldencode.p2j.net.Queue to eliminate
    **                           conflict with the same class from java.util
    **                           package to be able to compile for Java 6.
    ** 012 ECF 20071106   @35904 Replaced Queue with Session.
    ** 013 ECF 20150715          Replace StringBuffer with StringBuilder.
    ** 014 ECF 20150828          Enable Java 8.
    ** 015 TJD 20220504          Java 11 compatibility minor changes
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.test;

    import java.io.*;
    import java.util.*;
    import com.goldencode.p2j.directory.*;
    import com.goldencode.p2j.directory.Base64;
    import com.goldencode.p2j.net.*;

    /**
     * General purpose interactive directory editor.
     * <p>
     * Note that the use of <code>HighLevelObject</code> is not needed (and is
     * a significant amount of useless code) once we move the directory interfaces
     * into a <code>RemoteObject</code> approach.  Much of this code also exists
     * inside the <code>DirectoryEdit</code> class.  Very nasty.
     */
    public class DirectoryEdit
    {
       /** Current attribute name */
       String attrName = null;

       /** Locked branch */
       String branch = null;

       /** instance of HLO class that provides methods for directory access */
       ServerDirectory dir = null;

       /** Node menu definition/implementation */
       MenuItem[] nodeMenu = new MenuItem[]
       {
          new MenuItem(" ~.             Print menu")
             { void run(String[] args) { menuNodePrint(args); }},

          new MenuItem(" ~l             List attributes and nodes")
             { void run(String[] args) { menuNodeList(args); }},

          new MenuItem(" ~t             List attribute definitions")
             { void run(String[] args) { menuNodeListDefs(args); }},

          new MenuItem(" ~E node        Edit node")
             { void run(String[] args) { menuNodeEdit(args); }},

          new MenuItem(" ~C node [class] Create child node")
             { void run(String[] args) { menuNodeCreate(args); }},

          new MenuItem(" ~D node        Delete child node")
             { void run(String[] args) { menuNodeDelete(args); }},

          new MenuItem(" ~M node1 node2 Move node")
             { void run(String[] args) { menuNodeMove(args); }},

          new MenuItem(" ~e attribute   Edit node attribute")
             { void run(String[] args) { menuNodeEditAttribute(args); }},

          new MenuItem(" ~c attribute   Create node attribute")
             { void run(String[] args) { menuNodeCreateAttribute(args); }},

          new MenuItem(" ~d attribute   Delete node attribute")
             { void run(String[] args) { menuNodeDeleteAttribute(args); }},

          new MenuItem(" ~q             Quit")
             { void run(String[] args) { menuGoTop(args); }},
       };

       /** Top level menu definition/implementation */
       MenuItem[] topMenu = new MenuItem[]
       {
          new MenuItem(" ~.        Print menu")
             { void run(String[] args) { menuNodePrint(args); }},

          new MenuItem(" ~b node   Open batch for node")
             { void run(String[] args) { menuTopBatch(args); }},

          new MenuItem(" ~b        Back to batch node")
             { void run(String[] args) { menuNodePrint(args); }},

          new MenuItem(" ~c        Commit batch")
             { void run(String[] args) { menuTopCommit(args); }},

          new MenuItem(" ~r        Rollback batch")
             { void run(String[] args) { menuTopRollback(args); }},

          new MenuItem(" ~q        Quit")
             { void run(String[] args) { menuTopQuit(args); }},
       };

       /** Attribute editing menu */
       private MenuItem[] attrMenu = new MenuItem[]
       {
          new MenuItem(" ~.        Print menu")
             { void run(String[] args) { menuNodePrint(args); }},
          new MenuItem(" ~l        List values")
             { void run(String[] args) { menuAttrList(args); }},
          new MenuItem(" ~e n      Edit value n")
             { void run(String[] args) { menuAttrEdit(args); }},
          new MenuItem(" ~d n      Delete value n")
             { void run(String[] args) { menuAttrDelete(args); }},
          new MenuItem(" ~a        Add new value")
             { void run(String[] args) { menuAttrAdd(args); }},
          new MenuItem(" ~q        Quit")
             { void run(String[] args) { menuGoTop(args); }},
       };

       /**
        * Construct an instance.
        *
        * @param   session
        *          Reference to <code>Session</code>.
        */
       public DirectoryEdit(Session session)
       {
          dir = new ServerDirectory(session);
       }

       /**
        * A shortcut for getting user input.
        *
        * @return  User input as a string. The new line character is not part of
        *          string.
        */
       static String in()
       {
          StringBuilder sb = new StringBuilder();
          char ch = 0;

          while (true)
          {
             try
             {
                ch = (char)System.in.read();
             }
             catch (IOException x)
             {
                break;
             }
             if (ch == '\n')
                break;
             sb.append(ch);
          }

          return new String(sb);
       }

       /** A shortcut for the System.out.println() */
       static void say()
       {
          System.out.println();
       }

       /**
        * A shortcut for the System.out.println(String).
        *
        * @param   string
        *          String to print.
        */
       static void say(String string)
       {
          System.out.println(string);
       }

       /**
        * Adds a value to node's attribute.
        *
        * @param   node
        *          Node name.
        * @param   attr
        *          attribute name.
        * @param   newVal
        *          new value to be set.
        * @return  <code>true</code> if operation was successful.
        * @throws  Exception
        *          Forwarded from various methods.
        */
       boolean addValue(String node, String attr, String newVal)
       throws Exception
       {
          //int i;
          String className = getNodeClass(node);

          NodeAttribute nAttr = dir.getClassNodeAttribute(className, attr);

          if (nAttr == null)
             return false;

          int type = nAttr.getType();
          boolean res = false;

          switch (type)
          {
             case AttributeType.ATTR_INTEGER :
                res = dir.addNodeInteger(node, attr, Integer.parseInt(newVal));
                break;

             case AttributeType.ATTR_BOOLEAN :
                Boolean boVal = Boolean.valueOf(newVal);
                boolean bVal = boVal.booleanValue();
                res = dir.addNodeBoolean(node, attr, bVal);
                break;

             case AttributeType.ATTR_STRING :
                res = dir.addNodeString(node, attr, newVal);
                break;

             case AttributeType.ATTR_DOUBLE :
                res = dir.addNodeDouble(node, attr, Double.parseDouble(newVal));
                break;

             case AttributeType.ATTR_BYTEARRAY :
                res = dir.addNodeByteArray(node, attr,
                                           Base64.base64ToByteArray(newVal));
                break;

             case AttributeType.ATTR_BITFIELD :
                res = dir.addNodeBitField(node, attr, BitField.valueOf(newVal));
                break;

             case AttributeType.ATTR_BITSELECTOR :
                res = dir.addNodeBitSelector(
                                             node,
                                             attr,
                                             (BitSelector)BitSelector.valueOf(newVal));
                break;

             case AttributeType.ATTR_DATE :
                res = dir.addNodeDate(node, attr, DateValue.valueOf(newVal));
                break;

             case AttributeType.ATTR_TIME :
                res = dir.addNodeTime(node, attr, TimeValue.valueOf(newVal));
                break;

             default:
                ;
          }

          return res;
       }

       /**
        * Combine current branch ID and command arguments into resulting node ID.
        * <p>
        * If command arguments are missing then <code>null</code> is returned
        * indicating error, unless returning base branch ID is forced by setting
        * <code>force</code> parameter to <code>true</code>.
        *
        * @param   args
        *          Command arguments.
        * @param   force
        *          If set to <code>true</code> then resulting ID is returned
        *          regardless from the presence of the parameters.
        * @return  Resulting node ID.
        */
       String getFullId(String[] args, boolean force)
       {
          String node = branch;

          if (args != null && args.length > 1)
          {
             node += "/" + args[1];

             return node;
          }

          return (force) ? node : null;
       }

       /**
        * Get node class name without leading prefix.
        *
        * @param   node
        *          Node name.
        * @return  Node object class name.
        */
       String getNodeClass(String node)
       {
          try
          {
             String className = dir.getNodeClass(node);

             if (className != null)
                className = className.substring(className.lastIndexOf("/") + 1);
             return className;
          }
          catch (Exception e)
          {
             return null;
          }
       }

       /**
        * Gets the number of values of node's attribute.
        *
        * @param   node
        *          Node name.
        * @param   attr
        *          Attribute name.
        * @return  Number of values in the specified attribute of specified node.
        * @throws  Exception
        *          Forwarded from various methods.
        */
       int getNumValues(String node, String attr)
       throws Exception
       {
          //int i;
          String className = getNodeClass(node);

          NodeAttribute nAttr = dir.getClassNodeAttribute(className, attr);

          if (nAttr == null)
             return 0;

          int type = nAttr.getType();

          // getting values
          Object[] values = null;

          switch (type)
          {
             case AttributeType.ATTR_INTEGER :
                values = dir.getNodeIntegers(node, attr);
                break;

             case AttributeType.ATTR_BOOLEAN :
                values = dir.getNodeBooleans(node, attr);
                break;

             case AttributeType.ATTR_STRING :
                values = dir.getNodeStrings(node, attr);
                break;

             case AttributeType.ATTR_DOUBLE :
                values = dir.getNodeDoubles(node, attr);
                break;

             case AttributeType.ATTR_BYTEARRAY :
                values = dir.getNodeByteArrays(node, attr);
                break;

             case AttributeType.ATTR_BITFIELD :
                values = dir.getNodeBitFields(node, attr);
                break;

             case AttributeType.ATTR_BITSELECTOR :
                values = dir.getNodeBitSelectors(node, attr);
                break;

             case AttributeType.ATTR_DATE :
                values = dir.getNodeDates(node, attr);
                break;

             case AttributeType.ATTR_TIME :
                values = dir.getNodeTimes(node, attr);
                break;

             default:
                ;
          }

          if (values == null)
             return 0;

          return values.length;
       }

       /**
        * Check note if it is a terminal node and does not allow creation of child
        * nodes.
        *
        * @param   nodeId
        *          Node ID.
        * @return  <code>true</code> if node is a terminal node.
        */
       boolean isNodeTerminal(String nodeId)
       {
          try
          {
             String nodeClass = dir.getNodeClass(nodeId);

             if (nodeClass != null)
             {
                Boolean[] res = dir.getNodeBooleans(nodeClass, "terminal");

                if (res != null && res[0].booleanValue())
                   return true;
             }
          }
          catch (Exception e)
          {
          }

          return false;
       }

       /**
        * Lists the batch branch of the directory.
        *
        * @param   startingPoint
        *          Path (id) of the node to print
        * @throws  Exception
        *          Forwarded from various methods.
        */
       void listNodes(String startingPoint)
       throws Exception
       {
          printNodeAttrs(startingPoint, 1);
          printChildren(startingPoint, 1);
       }

       /**
        * Attribute menu - add attribute value implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuAttrAdd(String[] args)
       {
          if (args == null)
          {
             //Do nothing, just make Eclipse happy.
          }

          System.out.print("Enter new attribute value:");
          String value = in();

          try
          {
             if (addValue(branch, attrName, value))
                say("Value added successfully");
             else
                say("Operation failed");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Attribute menu - delete attribute value implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuAttrDelete(String[] args)
       {
          if (args.length != 2)
          {
             say("Value index needed");
             return;
          }

          try
          {
             int n = Integer.parseInt(args[1]);

             if (n < 0 || n > (getNumValues(branch, attrName) - 1))
             {
                say("Invalid index");
                return;
             }

             if (dir.deleteNodeAttributeValue(branch, attrName, n))
                say("Value deleted successfully");
             else
                say("Operation failed");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Attribute menu - edit attribute value implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuAttrEdit(String[] args)
       {
          if (args.length != 2)
          {
             say("Value index needed");
             return;
          }

          try
          {
             int n = Integer.parseInt(args[1]);

             if (n < 0 || n > (getNumValues(branch, attrName) - 1))
             {
                say("Invalid index");
                return;
             }

             String value = in();

             if (setValue(branch, attrName, n, value))
                say("Value changed successfully");
             else
                say("Operation failed");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Attribute menu - list attribute value implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuAttrList(String[] args)
       {
          if (args == null)
          {
             //Do nothing, just make Eclipse happy.
          }

          try
          {
             printAttrDef(branch, attrName);
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Return to previous level of menu.
        *
        * @param   args
        *          Command arguments (ignored).
        */
       void menuGoTop(String[] args)
       {
          if (args != null && args.length > 1)
             say("Redundant parameters ignored");

          say("Returning to previous menu");

          throw new StopMenu();
       }

       /**
        * Node menu - create object implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeCreate(String[] args)
       {
          String fullId = getFullId(args, false);

          if (fullId == null)
          {
             say("Node ID is required");
             return;
          }

          if (isNodeTerminal(branch))
          {
             say("Node it terminal. Menu item is disabled.");
          }

          try
          {
             String objClass;

             if (args.length > 2)
                objClass = args[2];
             else
             {
                System.out.print("Enter object class name>");
                System.out.flush();
                objClass = in();
             }

             if (!nodeExists("/meta/class/" + objClass))
             {
                say("Object class does not exists");
                return;
             }

             NodeAttribute[] list = dir.getClassNodeAttributes(objClass);

             Vector attrs = new Vector();
             Vector unfilled = new Vector();

             if (list != null)
             {
                //Check for presence of the mandatory attributes
                for (int i = 0; i < list.length; i++)
                {
                   if(list[i].isMandatory())
                   {
                      Object value = null;
                      Attribute attr = null;

                      while (true)
                      {
                         //say("Object class defines mandatory attribute \"" +
                         //    list[i].getName() + "\" (" +  + ")");
                         printDef(list[i], "Required attribute: ");
                         System.out.print("Enter value>");
                         System.out.flush();

                         String str = in();

                         value = stringToAttrValue(list[i], str);

                         if (value != null)
                         {
                            attr = new Attribute(list[i], new Object[] {value});

                            if (attr.isValid())
                               break;
                         }

                         say("Entered value is invalid.");
                      }

                      attrs.add(attr);
                   }
                   else
                   {
                      unfilled.add(list[i]);
                   }
                }

                if (unfilled.size() > 0)
                {
                   say("Enter <name value> pairs to add value to the attribute.");
                   say("Empty string breaks the loop.");
                   say("Following optional attributes can be added now:");

                   for (int i = 0; i < unfilled.size(); i++)
                   {
                      printDef((NodeAttribute)unfilled.get(i), "" + i + ") ");
                   }
                   say();

                   while (true)
                   {
                      Object value;
                      System.out.print("Enter name and value for attribute>");
                      System.out.flush();

                      String[] params = in().split(" ", 2);

                      if (params[0].equals(""))
                         break;

                      String val = null;

                      if (params.length < 2)
                         val = "";   //Empty value
                      else
                         val = params[1];

                      //Try to locate attribute in the array of already filled
                      //ones.

                      Attribute attr = null;
                      boolean found = false;

                      for (int i = 0; i < attrs.size(); i++)
                      {
                         Attribute tmpAttr =(Attribute)attrs.get(i);

                         if(tmpAttr.getName().equals(params[0]))
                         {
                            value = stringToAttrValue(tmpAttr.getDefinition(),
                                                      val);

                            attr = new Attribute(tmpAttr);

                            if (attr.addValue(value) && attr.isValid())
                            {
                               say("Added new value to attribute [" + params[0] +
                                   "].");
                               attrs.set(i, attr);
                            }
                            else
                               say("Entered value is invalid.");

                            found = true;
                            break;
                         }
                      }

                      if (found)
                         continue;

                      //Try to locate attribute definition

                      for (int i = 0; i < list.length; i++)
                      {
                         if(list[i].getName().equals(params[0]))
                         {
                            value = stringToAttrValue(list[i], val);

                            if (value != null)
                            {
                               attr = new Attribute(list[i], new Object[]
                                                                        {value});

                               if (attr.isValid())
                               {
                                  say("Added new attribute [" + params[0] + "].");
                                  attrs.add(attr);
                               }
                               else
                                  say("Entered value is invalid.");

                               found = true;
                               break;
                            }
                         }
                      }

                      if (found)
                         continue;

                      say("Unknown attribute name [" + params[0] + "].");
                   }
                }
             }

             boolean rc = false;

             if (attrs.size() == 0)
                rc = dir.addNode(fullId, objClass, null);
             else
                rc = dir.addNode(fullId, objClass,
                                 (Attribute[])attrs.toArray(new Attribute[0]));

             if (rc)
                say("Node created successfully.");
             else
                say("Node creation failed.");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - create attribute implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeCreateAttribute(String[] args)
       {
          if (args.length != 2)
          {
             say("Attribute name is required.");
             return;
          }

          System.out.print("Enter attribute value>");
          String value = in();

          try
          {
             if (addValue(branch, args[1], value))
                say("Attribute value added successfully.");
             else
                say("Operation failed.");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - delete object implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeDelete(String[] args)
       {
          String fullId = getFullId(args, false);

          if (fullId == null)
          {
             say("Node ID is required.");
             return;
          }

          try
          {
             if (!nodeExists(fullId))
                say(args[1] + " not found.");
             else
             {
                boolean rc = dir.deleteNode(fullId);

                if (rc)
                   say("Node deleted successfully.");
                else
                   say("Operation failed.");
             }
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - delete attribute implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeDeleteAttribute(String[] args)
       {
          if (args.length != 2)
          {
             say("Attribute name required.");
             return;
          }

          try
          {
             if (!printAttrDef(branch, args[1]))
             {
                say(args[1] + " not found.");
                return;
             }

             if (dir.deleteNodeAttribute(branch, args[1]))
                say("Attribute deleted successfully.");
             else
                say("Operation failed.");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - edit object implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeEdit(String[] args)
       {
          try
          {
             String nodeId = getFullId(args, false);

             if (nodeId == null)
             {
                say("Node ID is required.");
                return;
             }

             if (!nodeExists(nodeId))
             {
                say(nodeId + " not found.");
                return;
             }

             String save = branch;
             branch = nodeId;
             runNodeMenu();
             branch = save;
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - edit attribute implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeEditAttribute(String[] args)
       {
          if (args.length != 2)
          {
             say("Attribute name required.");
             return;
          }

          try
          {
             String className = getNodeClass(branch);

             if (className == null)
             {
                say("Unable to determine node class name.");
                return;
             }

             NodeAttribute nAttr = dir.getClassNodeAttribute(className, args[1]);

             if (nAttr == null)
             {
                say("No such attribute.");
                return;
             }

             attrName = args[1];

             run(branch + "." + args[1] + " menu:", attrMenu, null);

             attrName = null;
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - list attributes and objects implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeList(String[] args)
       {
          try
          {
             String nodeId = getFullId(args, true);
             listNodes(nodeId);
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - list definitions of attributes implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeListDefs(String[] args)
       {
          if (args == null)
          {
             //Do nothing, just make Eclipse happy.
          }

          try
          {
             printAttrDefs(branch);
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - move node implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodeMove(String[] args)
       {
          if (args.length != 3)
          {
             say("Source node ID and destination node ID are required.");
             return;
          }

          try
          {
             String fromID = getFullId(args, false);
             String toID = args[2];

             if (!toID.startsWith("/"))
                toID = branch + "/" + toID;

             boolean rc = dir.moveNode(fromID, toID);

             say("Node moving " + (rc ? "succeeded" : "failed"));
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Node menu - print menu implementation.
        *
        * @param   args
        *          Parsed command.
        */
       void menuNodePrint(String[] args)
       {
          //Do nothing
          if (args == null)
             return;
       }

       /**
        * Top menu - open batch implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuTopBatch(String[] args)
       {
          if (args == null)
          {
             say("Invalid input.");
             return;
          }

          if (branch != null)
          {
             runNodeMenu();
             return;
          }

          boolean rc = false;

          if (args.length > 1)
             branch = args[1];
          else
          {
             if (args[0].length() < 2)
             {
                say("Node ID is required.");
                return;
             }

             branch = args[0].substring(2).trim();
          }

          say("Opening batch for " + (branch.equals("") ? "root":branch));

          try
          {
             rc = dir.openBatch(branch);

             if (rc)
                runNodeMenu();
             else
                say("Batch open failed.");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }

          if (!rc)
             branch = null;
       }

       /**
        * Top menu - commit batch implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuTopCommit(String[] args)
       {
          if (args == null)
          {
             //Do nothing, just make Eclipse happy.
          }

          if (branch == null)
          {
             say("Batch is not open.");
             return;
          }

          try
          {
             if (dir.closeBatch(true))
                say("Changes committed successfully.");
             else
                say("Operation failed.");
          }
          catch (StopMenu s)
          {
             throw s;
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }

          branch = null;
       }

       /**
        * Top menu - quit implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuTopQuit(String[] args)
       {
          if (args == null)
          {
             //Do nothing, just make Eclipse happy.
          }

          if (branch != null)
          {
             say("Batch is still open. Commit or rollback it before exit.");
             return;
          }

          say("Exiting.");

          throw new StopMenu();
       }

       /**
        * Top menu - rollback batch implementation.
        *
        * @param   args
        *          Command arguments.
        */
       void menuTopRollback(String[] args)
       {
          if (args == null)
          {
             //Do nothing, just make Eclipse happy.
          }

          if (branch == null)
          {
             say("Batch is not open.");
             return;
          }

          try
          {
             if (dir.closeBatch(false))
                say("All changes are rolled back.");
             else
                say("Operation failed.");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }

          branch = null;
       }

       /**
        * Check existence of the node.
        *
        * @param   nodeId
        * @return  <code>true</code> is specified node exists.
        * @throws  Exception
        *          Forwarded from underlying call.
        */
       boolean nodeExists(String nodeId)
       throws Exception
       {
          return (dir.getNodeClass(nodeId) != null);
       }

       /**
        * Prints information about node's attribute definition.
        *
        * @param   node
        *          Node name.
        * @param   attr
        *          Attribute name.
        * @return  <code>true</code> if operation was successful.
        * @throws  Exception
        *          Forwarded from various methods.
        */
       boolean printAttrDef(String node, String attr)
       throws Exception
       {
          int i;
          StringBuilder buf = new StringBuilder(80);

          String className = getNodeClass(node);

          if (className == null)
             return false;

          NodeAttribute nAttr = dir.getClassNodeAttribute(className, attr);

          if (nAttr == null)
             return false;

          printDef(nAttr, null);

          // getting values
          Object[] values = null;

          switch (nAttr.getType())
          {
             case AttributeType.ATTR_INTEGER :
                values = dir.getNodeIntegers(node, attr);
                break;

             case AttributeType.ATTR_BOOLEAN :
                values = dir.getNodeBooleans(node, attr);
                break;

             case AttributeType.ATTR_STRING :
                values = dir.getNodeStrings(node, attr);
                break;

             case AttributeType.ATTR_DOUBLE :
                values = dir.getNodeDoubles(node, attr);
                break;

             case AttributeType.ATTR_BYTEARRAY :
                values = dir.getNodeByteArrays(node, attr);
                break;

             case AttributeType.ATTR_BITFIELD :
                values = dir.getNodeBitFields(node, attr);
                break;

             case AttributeType.ATTR_BITSELECTOR :
                values = dir.getNodeBitSelectors(node, attr);
                break;

             case AttributeType.ATTR_DATE :
                values = dir.getNodeDates(node, attr);
                break;

             case AttributeType.ATTR_TIME :
                values = dir.getNodeTimes(node, attr);
                break;

             default:
                ;
          }

          say("Values:");
          for (i = 0; values != null && i < values.length; i++)
          {
             buf.append("   " + i + ": ");
             buf.append(values[i].toString());
             say(buf.toString());
          }
          return true;
       }

       /**
        * Print definitions of attributes for the specified node.
        *
        * @param   node
        *          Node ID.
        */
       void printAttrDefs(String node)
       {
          try
          {
             String className = getNodeClass(node);

             if (className == null)
             {
                say("Unable to determine node class");
                return;
             }

             NodeAttribute[] attrs = dir.getClassNodeAttributes(className);

             say("Node [" + node + "] object class is \"" + className +"\".");

             if (attrs == null || attrs.length == 0)
             {
                say("Object class does not define attributes.");
                return;
             }

             say("Object class defines following attributes:");

             for (int i = 0; i < attrs.length; i++)
             {
                printDef(attrs[i], "   " + i + ") ");
             }
             say();
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Prints information about node's children nodes.
        *
        * @param   node
        *          Node name.
        * @param   level
        *          Nesting/indentation level.
        * @throws  Exception
        *          Forwarded from various methods.
        */
       void printChildren(String node, int level)
       throws Exception
       {
          String[] nodes = dir.enumerateNodes(node);
          int numNodes = nodes == null ? 0 : nodes.length;
          StringBuilder sb = new StringBuilder();
          int i = 0;

          for (i = 0; i < level; i++)
             sb.append(" ");

          say(sb.toString() + "Child nodes:");

          for (i = 0; i < numNodes; i++)
          {
             say(sb.toString() + i + ") " + nodes[i]);
          }
       }

       /**
        * Print instance of <code>NodeAttribute</code> in human-readable form.
        *
        * @param   nAttr
        *          Instance of the <code>NodeAttribute</code> to print.
        * @param   prefix
        *          Optional prefix to print before attribute definition.
        */
       void printDef(NodeAttribute nAttr, String prefix)
       {
          StringBuilder buf = new StringBuilder(80);

          int type = nAttr.getType();

          if (prefix != null)
             buf.append(prefix);

          buf.append("\"" + nAttr.getName() + "\" is ");
          buf.append(AttributeType.toString(type));
          buf.append(",");

          if (nAttr.isMandatory())
             buf.append(" mandatory,");
          else
             buf.append(" optional,");

          if (nAttr.isMultiple())
             buf.append(" multi-valued");
          else
             buf.append(" single-valued");

          say(buf.toString());
       }

       /**
        * Prints information about a single node.
        *
        * @param   path
        *          Path (id) of the parent node.
        * @param   node
        *          Node name.
        * @param   level
        *          Nesting/indentation level.
        * @throws  Exception
        *          Forwarded from various methods.
        */
       void printNode(String path, String node, int level)
       throws Exception
       {
          int i;
          StringBuilder buf = new StringBuilder(80);

          for (i = 0; i < level; i++)
             buf.append("  ");

          buf.append(node);
          buf.append(" {class = <");

          node = path + "/" + node;

          String name = dir.getNodeClass(node);

          buf.append(name);
          buf.append(">}");

          say(buf.toString());

          printNodeAttrs(node, level);
          printChildren(node, level);
       }

       /**
        * Prints information about node's attributes.
        *
        * @param   node
        *          Node name.
        * @param   level
        *          Nesting/indentation level.
        * @throws  Exception
        *          Forwarded from various methods.
        */
       void printNodeAttrs(String node, int level)
       throws Exception
       {
          int i;
          StringBuilder buf = new StringBuilder(80);

          for (i = 0; i < level; i++)
             buf.append("  ");

          String prefix = buf.toString();
          Attribute[] attr = dir.getNodeAttributes(node);

          if (attr != null)
          {
             for (i = 0; i < attr.length; i++)
             {
                Object val = attr[i].getValue(0);

                if (val == null)
                   continue;

                if (val instanceof byte[])
                   val = Base64.byteArrayToBase64((byte[])val);

                StringBuilder buf2 = new StringBuilder();

                buf2.append(prefix);
                buf2.append("  #attribute ");
                buf2.append(attr[i].getName());
                buf2.append(" = {");
                buf2.append(val.toString());

                for (int j = 1; val != null; j++)
                {
                   val = attr[i].getValue(j);

                   if (val != null)
                   {
                      if (val instanceof byte[])
                         val = Base64.byteArrayToBase64((byte[])val);
                      buf2.append(", ");
                      buf2.append(val.toString());
                   }
                }

                buf2.append("}");

                say(buf2.toString());
             }
          }
       }

       /** Main entry point */
       void run()
       {
          try
          {
             if (!dir.bind())
             {
                say("Binding to directory failed");
                return;
             }
             run("Top menu:", topMenu, null);

             if (!dir.unbind())
                say("Unbinding from directory failed");
          }
          catch (Exception e)
          {
             e.printStackTrace(System.out);
          }
       }

       /**
        * Menu list processing routine.
        *
        * @param   text
        *          Message which will be printed before menu.
        * @param   menu
        *          List of menu items.
        * @param   comment
        *          Additional comment message to display after menu.
        */
       void run(String text, MenuItem[] menu, String comment)
       {
          try
          {
             while (true)
             {
                //Draw menu

                say();
                say(text);

                for (int i = 0; i < menu.length; i++)
                   say(menu[i].getText());

                say();
                if (comment != null)
                {
                   say(comment);
                   say();
                }

                System.out.print("Enter command>");
                System.out.flush();

                boolean found = false;

                String input = in();
                if (input.length() == 0)
                      continue;

                char ch = input.charAt(0);

                for (int i = 0; i < menu.length; i++)
                {
                   if (menu[i].getHotKey() == ch)
                   {
                      found = true;
                      String[] res = input.split(" ");

                      res[0] = input;

                      menu[i].run(res);
                   }
                }

                if (!found)
                   say("Invalid command [" + input + "]");
             }
          }
          catch (StopMenu e)
          {
          }
       }

       /**
        * Prepare message and call node menu.
        */
       void runNodeMenu()
       {
          String message = null;

          if (isNodeTerminal(branch))
             message = "Node is terminal, menu item 'C node [class]' is disabled";

          run((branch.equals("") ? "root" : branch) + " node menu:", nodeMenu,
              message);
       }

       /**
        * Sets a value of node's attribute.
        *
        * @param   node
        *          Node name.
        * @param   attr
        *          Attribute name.
        * @param   n
        *          Value index.
        * @param   newVal
        *          New value to be set.
        * @return  <code>true</code> if operation was successful.
        * @throws  Exception
        *          Forwarded from various methods.
        */
       boolean setValue(String node, String attr, int n, String newVal)
       throws Exception
       {
          //int i;
          String className = getNodeClass(node);

          NodeAttribute nAttr = dir.getClassNodeAttribute(className, attr);

          if (nAttr == null)
             return false;

          int type = nAttr.getType();
          boolean res = false;

          switch (type)
          {
             case AttributeType.ATTR_INTEGER :
                res = dir.setNodeInteger(node, attr, n, Integer.parseInt(newVal));
                break;

             case AttributeType.ATTR_BOOLEAN :
                Boolean boVal = Boolean.valueOf(newVal);
                boolean bVal = boVal.booleanValue();
                res = dir.setNodeBoolean(node, attr, n, bVal);
                break;

             case AttributeType.ATTR_STRING :
                res = dir.setNodeString(node, attr, n, newVal);
                break;

             case AttributeType.ATTR_DOUBLE :
                res = dir.setNodeDouble(node, attr, n, Double.parseDouble(newVal));
                break;

             case AttributeType.ATTR_BYTEARRAY :
                res = dir.setNodeByteArray(node, attr, n,
                                           Base64.base64ToByteArray(newVal));
                break;

             case AttributeType.ATTR_BITFIELD :
                res = dir.setNodeBitField(node, attr, n, BitField.valueOf(newVal));
                break;

             case AttributeType.ATTR_BITSELECTOR :
                res = dir.setNodeBitSelector(
                                             node,
                                             attr,
                                             n,
                                             (BitSelector)BitSelector.valueOf(newVal));
                break;

             case AttributeType.ATTR_DATE :
                res = dir.setNodeDate(node, attr, n, DateValue.valueOf(newVal));
                break;

             case AttributeType.ATTR_TIME :
                res = dir.setNodeTime(node, attr, n, TimeValue.valueOf(newVal));
                break;

             default:
                ;
          }

          return res;
       }

       /**
        * Convert given string value into attribute value using attribute type
        * information.
        *
        * @param   list
        *          Attribute definition list.
        * @param   str
        *          Source string.
        * @return  Converted value.
        */
       Object stringToAttrValue(NodeAttribute list, String str)
       {
          Object value = null;

          switch(list.getType())
          {
             case AttributeType.ATTR_STRING :
                value = str;
                break;

             case AttributeType.ATTR_BYTEARRAY :
                value = Base64.base64ToByteArray(str);
                break;

             case AttributeType.ATTR_INTEGER :
                value = Integer.valueOf(str, 10);
                break;

             case AttributeType.ATTR_BOOLEAN :
                value = Boolean.valueOf(str);
                break;

             case AttributeType.ATTR_DOUBLE :
                value = Double.valueOf(str);
                break;

             case AttributeType.ATTR_BITFIELD :
                value = BitField.valueOf(str);
                break;

             case AttributeType.ATTR_BITSELECTOR :
                value = BitSelector.valueOf(str);
                break;

             case AttributeType.ATTR_DATE :
                value = DateValue.valueOf(str);
                break;

             case AttributeType.ATTR_TIME :
                value = TimeValue.valueOf(str);
                break;
          }
          return value;
       }

       /**
        * Simple command line harness.
        */
       public static void main(String[] args)
       {
          // TODO: this application is non-functional at this time, we need to
          //       1. prepare the bootstrap config
          //       2. make our connection and get our queue
          //       3. start the queue

          Session session = null;

          try
          {
             DirectoryEdit app = new DirectoryEdit(session);

             try
             {
                app.run();
             }

             catch (Exception x)
             {
                say("Exception in topMenu(): " + x);
                x.printStackTrace(System.err);
             }
          }

          catch (Exception e)
          {
             e.printStackTrace(System.out);
             System.exit(-1);
          }
       }

       /**
        * HLO which will map server entry points into local object.
        */
       public static class ServerDirectory
       extends HighLevelObject
       {
          /** Group of exported methods which will be used for testing */
          static final String GROUP = "directory";

          /** RoutingKey for addNodeBitField() method */
          private RoutingKey addNodeBitFieldKey = null;

          /** RoutingKey for addNodeBitSelector() method */
          private RoutingKey addNodeBitSelectorKey = null;

          /** RoutingKey for addNodeBoolean() method */
          private RoutingKey addNodeBooleanKey = null;

          /** RoutingKey for addNodeByteArray() method */
          private RoutingKey addNodeByteArrayKey = null;

          /** RoutingKey for addNodeDate() method */
          private RoutingKey addNodeDateKey = null;

          /** RoutingKey for addNodeDouble() method */
          private RoutingKey addNodeDoubleKey = null;

          /** RoutingKey for addNodeInteger() method */
          private RoutingKey addNodeIntegerKey = null;

          /** RoutingKey for addNode() method */
          private RoutingKey addNodeKey = null;

          /** RoutingKey for addNodeString() method */
          private RoutingKey addNodeStringKey = null;

          /** RoutingKey for addNodeTime() method */
          private RoutingKey addNodeTimeKey = null;

          /** RoutingKey for bind() method */
          private RoutingKey bindKey = null;

          /** RoutingKey for closeBatch() method */
          private RoutingKey closeBatchKey = null;

          /** RoutingKey for deleteNodeAttribute() method */
          private RoutingKey deleteNodeAttributeKey = null;

          /** RoutingKey for deleteNodeAttributeValue() method */
          private RoutingKey deleteNodeAttrValueKey = null;

          /** RoutingKey for deleteNode() method */
          private RoutingKey deleteNodeKey = null;

          /** RoutingKey for enumerateNodes() method */
          private RoutingKey enumNodesKey = null;

          /** RoutingKey for getClassNodeAttribute() method */
          private RoutingKey getClassNodeAttrKey = null;

          /** RoutingKey for getClassNodeAttributes() method */
          private RoutingKey getClassNodeAttrsKey = null;

          /** RoutingKey for getNodeAttributes() method */
          private RoutingKey getNodeAttrsKey = null;

          /** RoutingKey for getNodeBitFields() method */
          private RoutingKey getNodeBitFieldsKey = null;

          /** RoutingKey for getNodeBitSelectors() method */
          private RoutingKey getNodeBitSelectorsKey = null;

          /** RoutingKey for getNodeBooleans() method */
          private RoutingKey getNodeBooleansKey = null;

          /** RoutingKey for getNodeByteArrays() method */
          private RoutingKey getNodeByteArraysKey = null;

          /** RoutingKey for getNodeClass() method */
          private RoutingKey getNodeClassKey = null;

          /** RoutingKey for getNodeDates() method */
          private RoutingKey getNodeDatesKey = null;

          /** RoutingKey for getNodeDoubles() method */
          private RoutingKey getNodeDoublesKey = null;

          /** RoutingKey for getNodeIntegers() method */
          private RoutingKey getNodeIntegersKey = null;

          /** RoutingKey for getNodeStrings() method */
          private RoutingKey getNodeStringsKey = null;

          /** RoutingKey for getNodeTimes() method */
          private RoutingKey getNodeTimesKey = null;

          /** RoutingKey for moveNode() method */
          private RoutingKey moveNodeKey = null;

          /** RoutingKey for openBatch() method */
          private RoutingKey openBatchKey = null;

          /** RoutingKey for setNodeBitField() method */
          private RoutingKey setNodeBitFieldKey = null;

          /** RoutingKey for setNodeBitSelector() method */
          private RoutingKey setNodeBitSelectorKey = null;

          /** RoutingKey for setNodeBoolean() method */
          private RoutingKey setNodeBooleanKey = null;

          /** RoutingKey for setNodeByteArray() method */
          private RoutingKey setNodeByteArrayKey = null;

          /** RoutingKey for setNodeDate() method */
          private RoutingKey setNodeDateKey = null;

          /** RoutingKey for setNodeDouble() method */
          private RoutingKey setNodeDoubleKey = null;

          /** RoutingKey for setNodeInteger() method */
          private RoutingKey setNodeIntegerKey = null;

          /** RoutingKey for setNodeString() method */
          private RoutingKey setNodeStringKey = null;

          /** RoutingKey for setNodeTime() method */
          private RoutingKey setNodeTimeKey = null;

          /** RoutingKey for unbind() method */
          private RoutingKey unbindKey = null;

          /**
           * Construct an HLO using provided instance of the <code>Queue</code>.
           *
           * @param   session
           *          Properly initialized session.
           */
          ServerDirectory(Session session)
          {
             super(session, 60000);
          }

          /**
           * Add new node of specified class with specified ID. For node
           * initialization list of attributes and corresponding list of attribute
           * values should be provided. The presence of the mandatory attributes
           * in the lists is obligatory.
           *
           * @param   nodeId
           *          Node name.
           * @param   nodeClass
           *          Node class name.
           * @param   data
           *          A list of the attributes.
           * @return  <code>true</code> if operation was successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNode(String nodeId, String nodeClass, Attribute[] data)
          throws Exception
          {
             if (addNodeKey == null)
                addNodeKey = getKey(GROUP, "addnode");
             if (addNodeKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, nodeClass, data };

             Object result = transact(addNodeKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>BitField</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeBitField(String nodeId, String name,
                                         BitField value)
          throws Exception
          {
             if (addNodeBitFieldKey == null)
                addNodeBitFieldKey = getKey(GROUP, "addnodebitfield");
             if (addNodeBitFieldKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, value
                };

             Object result = transact(addNodeBitFieldKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>BitSelector</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeBitSelector(String nodeId, String name,
                                            BitSelector value)
          throws Exception
          {
             if (addNodeBitSelectorKey == null)
                addNodeBitSelectorKey = getKey(GROUP, "addnodebitselector");
             if (addNodeBitSelectorKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, value
                };

             Object result = transact(addNodeBitSelectorKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>boolean</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeBoolean(String nodeId, String name, boolean value)
          throws Exception
          {
             if (addNodeBooleanKey == null)
                addNodeBooleanKey = getKey(GROUP, "addnodeboolean");
             if (addNodeBooleanKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Boolean.valueOf(value)
                };

             Object result = transact(addNodeBooleanKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>byte[]</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeByteArray(String nodeId, String name, byte[] value)
          throws Exception
          {
             if (addNodeByteArrayKey == null)
                addNodeByteArrayKey = getKey(GROUP, "addnodebytearray");
             if (addNodeByteArrayKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, value
                };

             Object result = transact(addNodeByteArrayKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>DateValue</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeDate(String nodeId, String name, DateValue value)
          throws Exception
          {
             if (addNodeDateKey == null)
                addNodeDateKey = getKey(GROUP, "addnodedate");
             if (addNodeDateKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, value
                };

             Object result = transact(addNodeDateKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>Double</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeDouble(String nodeId, String name, double value)
          throws Exception
          {
             if (addNodeDoubleKey == null)
                addNodeDoubleKey = getKey(GROUP, "addnodedouble");
             if (addNodeDoubleKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Double.valueOf(value)
                };

             Object result = transact(addNodeDoubleKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>int</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeInteger(String nodeId, String name, int value)
          throws Exception
          {
             if (addNodeIntegerKey == null)
                addNodeIntegerKey = getKey(GROUP, "addnodeinteger");
             if (addNodeIntegerKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Integer.valueOf(value)
                };

             Object result = transact(addNodeIntegerKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>String</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeString(String nodeId, String name, String value)
          throws Exception
          {
             if (addNodeStringKey == null)
                addNodeStringKey = getKey(GROUP, "addnodestring");
             if (addNodeStringKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, value
                };

             Object result = transact(addNodeStringKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Add new <code>TimeValue</code> value to the attribute.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean addNodeTime(String nodeId, String name, TimeValue value)
          throws Exception
          {
             if (addNodeTimeKey == null)
                addNodeTimeKey = getKey(GROUP, "addnodetime");
             if (addNodeTimeKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, value
                };

             Object result = transact(addNodeTimeKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Binds to the directory.
           *
           * @return  <code>true</code> is operation is successful and
           *         <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean bind()
          throws Exception
          {
             if (bindKey == null)
                bindKey = getKey(GROUP, "bind");
             if (bindKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] {};
          Object result = transact(bindKey, parms);

          return ((Boolean)result).booleanValue();
       }

          /**
           * Closes the currently open editing batch.
           *
           * @param   disposition
           *          true if committing, false otherwise
           * @return  true or false
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean closeBatch(boolean disposition)
          throws Exception
          {
             if (closeBatchKey == null)
                closeBatchKey = getKey(GROUP, "closebatch");
             if (closeBatchKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   Boolean.valueOf(disposition)
                };
             Object result = transact(closeBatchKey, parms);

             return ((Boolean)result).booleanValue();
          }

          /**
           * Remove specified node from the directory (unless node has children).
           *
           * @param   nodeId
           *          Node identifier.
           * @return  <code>true</code> if operation was successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean deleteNode(String nodeId)
          throws Exception
          {
             if (deleteNodeKey == null)
                deleteNodeKey = getKey(GROUP, "deletenode");
             if (deleteNodeKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId
                };

             Object result = transact(deleteNodeKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Remove whole node attribute.
           *
           * @param   nodeId
           *          Node name.
           * @param   name
           *          Attribute name.
           * @return  <code>true</code> if operation was successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean deleteNodeAttribute(String nodeId, String name)
          throws Exception
          {
             if (deleteNodeAttributeKey == null)
                deleteNodeAttributeKey = getKey(GROUP, "deletenodeattribute");
             if (deleteNodeAttributeKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name
                };

             Object result = transact(deleteNodeAttributeKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Remove particular value from the node attribute. Note that removing
           * last value from the attribute deletes entire attribute unless
           * attribute is marked mandatory.
           *
           * @param   nodeId
           *          Node name.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Attribute value index.
           * @return  <code>true</code> if operation was successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean deleteNodeAttributeValue(String nodeId, String name,
                                                  int index)
          throws Exception
          {
             if (deleteNodeAttrValueKey == null)
                deleteNodeAttrValueKey = getKey(GROUP, "deletenodeattributevalue");
             if (deleteNodeAttrValueKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name, Integer.valueOf(index) };

             Object result = transact(deleteNodeAttrValueKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Enumerates all children nodes of a node.
           *
           * @param   nodeId
           *          Node name
           * @return  Array of children node IDs
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public String[] enumerateNodes(String nodeId)
          throws Exception
          {
             if (enumNodesKey == null)
                enumNodesKey = getKey(GROUP, "enumeratenodes");
             if (enumNodesKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId };

             Object result = transact(enumNodesKey, parms);

             return (String[])result;
          }

          /**
           * Gets NodeAttribute structure for the specified attribute.
           *
           * @param   className
           *          Object Class name.
           * @param   name
           *          Object Class attribute name.
           * @return  An instance of <code>NodeAttribute</code> which describes
           *          specified attribute or <code>null</code> if no such object
           *          class or attribute name.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public NodeAttribute getClassNodeAttribute(String className, String name)
          throws Exception
          {
             if (getClassNodeAttrKey == null)
                getClassNodeAttrKey = getKey(GROUP, "getclassnodeattribute");
             if (getClassNodeAttrKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { className, name };

             Object result = transact(getClassNodeAttrKey, parms);

             return (NodeAttribute)result;
          }

          /**
           * Generate an array of <code>NodeAttribute</code> structures for the
           * all attributes defined for the node class. Note that since these
           * instances of <code>NodeAttribute</code> are not attached to
           * particular instance of <code>Attribute</code>, information about
           * number of the values assigned to the attribute is incorrect. The
           * purpose of this method is to create new attributes.
           *
           * @param   className
           *          Object Class name.
           * @return  An array of <code>NodeAttribute</code> instances which
           *          describe attributes of node or <code>null</code> if no such
           *          object class or attributes defined for the class.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public NodeAttribute[] getClassNodeAttributes(String className)
          throws Exception
          {
             if (getClassNodeAttrsKey == null)
                getClassNodeAttrsKey = getKey(GROUP, "getclassnodeattributes");
             if (getClassNodeAttrsKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]  { className };

             Object result = transact(getClassNodeAttrsKey, parms);

             return (NodeAttribute[])result;
          }

          /**
           * Reads all attributes of a node.
           *
           * @param   nodeId
           *          Node name
           * @return  Array of node attributes
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public Attribute[] getNodeAttributes(String nodeId)
          throws Exception
          {
             if (getNodeAttrsKey == null)
                getNodeAttrsKey = getKey(GROUP, "getnodeattributes");
             if (getNodeAttrsKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId };

             Object result = transact(getNodeAttrsKey, parms);

             return (Attribute[])result;
          }

          /**
           * Gets all <code>BitField</code> attribute values for specified node
           * ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public BitField[] getNodeBitFields(String nodeId, String name)
          throws Exception
          {
             if (getNodeBitFieldsKey == null)
                getNodeBitFieldsKey = getKey(GROUP, "getnodebitfields");
             if (getNodeBitFieldsKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeBitFieldsKey, parms);

             return (BitField[])result;
          }

          /**
           * Gets all <code>BitSelector</code> attribute values for specified
           * node ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public BitSelector[] getNodeBitSelectors(String nodeId, String name)
          throws Exception
          {
             if (getNodeBitSelectorsKey == null)
                getNodeBitSelectorsKey = getKey(GROUP, "getnodebitselectors");
             if (getNodeBitSelectorsKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeBitSelectorsKey, parms);

             return (BitSelector[])result;
          }

          /**
           * Gets all <code>Boolean</code> attribute values for specified node
           * ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public Boolean[] getNodeBooleans(String nodeId, String name)
          throws Exception
          {
             if (getNodeBooleansKey == null)
                getNodeBooleansKey = getKey(GROUP, "getnodebooleans");
             if (getNodeBooleansKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeBooleansKey, parms);

             return (Boolean[])result;
          }

          /**
           * Gets all <code>ByteArray</code> attribute values for specified node
           * ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public byte[][] getNodeByteArrays(String nodeId, String name)
          throws Exception
          {
             if (getNodeByteArraysKey == null)
                getNodeByteArraysKey = getKey(GROUP, "getnodebytearrays");
             if (getNodeByteArraysKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeByteArraysKey, parms);

             return (byte[][])result;
          }

          /**
           * Queries the object class name of a node.
           *
           * @param   nodeId
           *          Node name
           * @return  Class name of this node
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public String getNodeClass(String nodeId)
          throws Exception
          {
             if (getNodeClassKey == null)
                getNodeClassKey = getKey(GROUP, "getnodeclass");
             if (getNodeClassKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId };

             Object result = transact(getNodeClassKey, parms);

             return (String)result;
          }

          /**
           * Gets all <code>Date</code> attribute values for specified node ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public DateValue[] getNodeDates(String nodeId, String name)
          throws Exception
          {
             if (getNodeDatesKey == null)
                getNodeDatesKey = getKey(GROUP, "getnodedates");
             if (getNodeDatesKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeDatesKey, parms);

             return (DateValue[])result;
          }

          /**
           * Gets all <code>Double</code> attribute values for specified node
           * ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public Double[] getNodeDoubles(String nodeId, String name)
          throws Exception
          {
             if (getNodeDoublesKey == null)
                getNodeDoublesKey = getKey(GROUP, "getnodedoubles");
             if (getNodeDoublesKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeDoublesKey, parms);

             return (Double[])result;
          }

          /**
           * Gets all <code>Integer</code> attribute values for specified node
           * ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public Integer[] getNodeIntegers(String nodeId, String name)
          throws Exception
          {
             if (getNodeIntegersKey == null)
                getNodeIntegersKey = getKey(GROUP, "getnodeintegers");
             if (getNodeIntegersKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeIntegersKey, parms);

             return (Integer[])result;
          }

          /**
           * Gets all <code>String</code> attribute values for specified node
           * ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public String[] getNodeStrings(String nodeId, String name)
          throws Exception
          {
             if (getNodeStringsKey == null)
                getNodeStringsKey = getKey(GROUP, "getnodestrings");
             if (getNodeStringsKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeStringsKey, parms);

             return (String[])result;
          }

          /**
           * Gets all <code>Time</code> attribute values for specified node ID.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @return  Values of the attribute
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public TimeValue[] getNodeTimes(String nodeId, String name)
          throws Exception
          {
             if (getNodeTimesKey == null)
                getNodeTimesKey = getKey(GROUP, "getnodetimes");
             if (getNodeTimesKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name };

             Object result = transact(getNodeTimesKey, parms);

             return (TimeValue[])result;
          }

          /**
           * Renames the existing object, specified by nodeId, or moves it to a
           * different location. The parent object of the newId should exist and
           * allow for children creation.
           *
           * @param   nodeId
           *          An ID of the node to move.
           * @param   newId
           *          The desired new location of the node.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean moveNode(String nodeId, String newId)
          throws Exception
          {
             if (moveNodeKey == null)
                moveNodeKey = getKey(GROUP, "movenode");
             if (moveNodeKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, newId };

             Object result = transact(moveNodeKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Opens an editing batch.
           *
           * @param   nodeId
           *          Node specifying directory branch to open
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean openBatch(String nodeId)
          throws Exception
          {
             if (openBatchKey == null)
                openBatchKey = getKey(GROUP, "openbatch");
             if (openBatchKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId };
             Object result = transact(openBatchKey, parms);

             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>BitField</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeBitField(String nodeId, String name, int index,
                                         BitField value)
          throws Exception
          {
             if (setNodeBitFieldKey == null)
                setNodeBitFieldKey = getKey(GROUP, "setnodebitfield4");
             if (setNodeBitFieldKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name, Integer.valueOf(index),
                                             value };

             Object result = transact(setNodeBitFieldKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>BitSelector</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeBitSelector(String nodeId, String name,
                                            int index, BitSelector value)
          throws Exception
          {
             if (setNodeBitSelectorKey == null)
                setNodeBitSelectorKey = getKey(GROUP, "setnodebitselector4");
             if (setNodeBitSelectorKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name, Integer.valueOf(index),
                                             value};

             Object result = transact(setNodeBitSelectorKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>boolean</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeBoolean(String nodeId, String name, int index,
                                        boolean value)
          throws Exception
          {
             if (setNodeBooleanKey == null)
                setNodeBooleanKey = getKey(GROUP, "setnodeboolean4");
             if (setNodeBooleanKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Integer.valueOf(index), Boolean.valueOf(value)
                };

             Object result = transact(setNodeBooleanKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>byte[]</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeByteArray(String nodeId, String name, int index,
                                          byte[] value)
          throws Exception
          {
             if (setNodeByteArrayKey == null)
                setNodeByteArrayKey = getKey(GROUP, "setnodebytearray4");
             if (setNodeByteArrayKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Integer.valueOf(index), value
                };

             Object result = transact(setNodeByteArrayKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>DateValue</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeDate(String nodeId, String name, int index,
                                     DateValue value)
          throws Exception
          {
             if (setNodeDateKey == null)
                setNodeDateKey = getKey(GROUP, "setnodedate4");
             if (setNodeDateKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] { nodeId, name, Integer.valueOf(index),
                                              value };

             Object result = transact(setNodeDateKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>double</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeDouble(String nodeId, String name, int index,
                                       double value)
          throws Exception
          {
             if (setNodeDoubleKey == null)
                setNodeDoubleKey = getKey(GROUP, "setnodedouble4");
             if (setNodeDoubleKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Integer.valueOf(index), Double.valueOf(value)
                };

             Object result = transact(setNodeDoubleKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>int</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeInteger(String nodeId, String name, int index,
                                        int value)
          throws Exception
          {
             if (setNodeIntegerKey == null)
                setNodeIntegerKey = getKey(GROUP, "setnodeinteger4");
             if (setNodeIntegerKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Integer.valueOf(index), Integer.valueOf(value)
                };

             Object result = transact(setNodeIntegerKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>String</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeString(String nodeId, String name, int index,
                                       String value)
          throws Exception
          {
             if (setNodeStringKey == null)
                setNodeStringKey = getKey(GROUP, "setnodestring4");
             if (setNodeStringKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Integer.valueOf(index), value
                };

             Object result = transact(setNodeStringKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Set <code>TimeValue</code> attribute value for specified node ID,
           * attribute name and index.
           *
           * @param   nodeId
           *          Node identifier.
           * @param   name
           *          Attribute name.
           * @param   index
           *          Index of the value to retrieve.
           * @param   value
           *          New value for the attribute variable.
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean setNodeTime(String nodeId, String name, int index,
                                     TimeValue value)
          throws Exception
          {
             if (setNodeTimeKey == null)
                setNodeTimeKey = getKey(GROUP, "setnodetime4");
             if (setNodeTimeKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[]
                {
                   nodeId, name, Integer.valueOf(index), value
                };

             Object result = transact(setNodeTimeKey, parms);
             return ((Boolean)result).booleanValue();
          }

          /**
           * Unbinds from directory service.
           *
           * @return  <code>true</code> is operation is successful and
           *          <code>false</code> otherwise.
           * @throws  Exception
           *          Forwarded from the remote method.
           */
          public boolean unbind()
          throws Exception
          {
             if (unbindKey == null)
                unbindKey = getKey(GROUP, "unbind");
             if (unbindKey == null)
                throw new Exception("unresolvable remote export");

             Object[] parms = new Object[] {};
             Object result = transact(unbindKey, parms);

             return ((Boolean)result).booleanValue();
          }
       }

       /**
        * Single menu item.
        */
       abstract class MenuItem
       {
          /** Menu item selection key */
          char hotkey;

          /** Menu item text */
          String text;

          /**
           * Construct an instance with given menu text. Menu item text must
           * contain a menu item selection key marked by leading tilde (~)
           * character. For example: <br>
           * <code>First menu item - ~a</code> will mark <b>a </b> as selection
           * key.
           *
           * @param   text
           *          Menu item text.
           */
          MenuItem(String text)
          {
             prepareText(text);
          }

          /**
           * Get menu item selection key.
           *
           * @return  Menu item selection key.
           */
          public char getHotKey()
          {
             return hotkey;
          }

          /**
           * Get menu item text with making tilde removed.
           *
           * @return  Menu item text.
           */
          public String getText()
          {
             return text;
          }

          /**
           * Worker routing which is executed when menu item is selected.
           *
           * @param   args
           *          Array of tokens retrieved from the user input.
           */
          abstract void run(String[] args);

          /**
           * Process menu item text and retrieve selection key. Then marker (~) is
           * removed.
           *
           * @param   str
           *          Menu item text.
           * @throws  RuntimeException
           *          If menu item does not contain proper marker.
           */
          private void prepareText(String str)
          {
             int pos = str.indexOf('~');

             if (pos < 0 || pos >= (str.length() - 1))
                throw new RuntimeException("Menu item [" + str
                   + "] does not have hot key.");

             hotkey = str.charAt(pos + 1);
             text = str.substring(0, pos) + str.substring(pos + 1);
          }
       }

       /**
        * Special exception used to break menu processing loop. <br>
        * Since only few (usually one from the entire list) menu item throws this
        * exception <code>RuntimeException</code> is used as a base because it
        * does not need to be declared in each method.
        */
       static class StopMenu
       extends RuntimeException
       {
          /**
           * Construct an empty exception.
           */
          StopMenu()
          {
             super();
          }
       }
    }