/* **************************************************************************
 %name: shell.java %
 %version: 14 %
 %date_modified: Thu Feb 19 15:51:29 1998 %

 Copyright (c) 1997-1999 Novell, Inc. All Rights Reserved.

 THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
 USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
 ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK.
 PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
 ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
 PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
 DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
 PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
 CUSTOMERS WITH RESPECT TO THIS CODE.
****************************************************************************/

import java.io.*;
import java.util.*;

import com.novell.service.session.*;
import com.novell.service.session.util.Debug;
import com.novell.service.session.xplat.PersistenceService;
import com.novell.service.session.xplat.Version;
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.spi.*;
import com.novell.java.lang.HasRootCause;
import com.novell.service.session.xplat.Xplat;
import com.novell.service.session.xplat.Address;
import com.novell.java.security.*;
import com.novell.service.security.Password;
import com.novell.service.security.PasswordIdentity;
import com.novell.service.security.PasswordIdentityFactory;

/**
 * Sets up a useful environment for the SessionShell class to operate in.
 *
 * <p>The method main() will interpret any strings passed to it as
 * command line parameters.
 *
 * <p>The method runShell() will run the shell given a set of minimal
 * options and the necessary input and output streams.
 *
 * @author John Sumsion, Novell, Inc.
 * @version 1.1.5
 */

public class shell
{
   /**
    * Program identification for the command line.
    */
   public static final String helpStr = "SessionShell (c) 1997-1999 " +
         "Novell, Inc.";

   /**
    * Takes parameters from the command line and spawns a SessionShell
    * according to the options specified.  To get a list of valid parameters
    * run this class with the -? or -h command line options.  This method
    * contains all the System.out.println calls, and any special policies
    * for loading a startup file.  The shell is actually spawned by the
    * runShell() method.  Therefore, if you wanted to use this shell in
    * a browser or other environment where System.in and System.out and File
    * don't mean much, then set everything up and call the runShell()
    * method with the appropriate parameters.
    *
    * @param      argv           (in) command line parameters
    */
   public static void main (String[] argv)
   {
      System.out.println (shell.helpStr);

      boolean continueWStdIn = false;
      boolean verbose = false;
      boolean startDS = false;
      String iniFile = "shell.ini";
      int startArgs = 0;  // used to mark last non-option cmd. line param.

      // cycle through the command line
      for (int a = 0; a < argv.length; a++)
      {
         // if it's not an option, then break this loop and assume
         // no more options are in the command line
         if (argv[a].startsWith ("-"))
         {
            // get the option minus the '-' char
            String option = argv[a].substring (1);

            // verbose means that a stack dump is printed for any exceptions
            if (option.equals ("verbose"))
               verbose = true;
            else if (option.equals ("ini"))
               iniFile = argv[++a];
            // help is displayed for -? and -h options
            else if (option.toUpperCase ().equals ("H") ||
                option.equals ("?"))
            {
               String[] helpStrs =
               {
                  "usage: java shell [-verbose] [-ds] [<input> [output]]",
                  "",
                  "  -verbose    stack trace printed on all exceptions",
                  "  -ini <file> specify file to retrieve settings from " +
                     "(default: -ini shell.ini)",
                  "  input       input file from which to read commands",
                  "  output      output file to which to write results",
                  "",
                  "If an input file is specified, but no output file,",
                  "then control is returned to the terminal after",
                  "executing all input commands.",
                  "",
                  "'#' and ';' are comment characters."
               };

               for (int x = 0; x < helpStrs.length; x++)
                  System.out.println (helpStrs[x]);

               return;
            }
            else
            {
               System.out.println ("error: invalid option: " + option);
               return;
            }
            startArgs = a + 1;
         }
         else
            break;
         // bump up the non-option pointer
      }

      Properties defaults = new Properties();
      defaults.put(
         SessionEnv.INITIAL_SESSION_FACTORY,
         "com.novell.service.session.nds.NDSInitialSessionFactory:" +
         "com.novell.service.session.bindery.BinderyInitialSessionFactory");
      defaults.put(
         SessionEnv.ALLOW_BACKGROUND_VALIDATION,
         "false");
      defaults.put(
         "private",
         "false");
      defaults.put(
         "debug.properties",
         "shell.deb");

      Properties props = new Properties (defaults);
      File file = new File (iniFile);  // startup file, contains props

      // look for the startup file
      if (file.exists ())
      {
         try
         {
            // load any properties specified in the startup file
            props.load (new FileInputStream (file));
         }
         catch (IOException e)
         {
            System.out.println ("error: reading shell startup properties");
         }
      }

      InputStream in = System.in;
      OutputStream out = System.out;

      try
      {
         // get first non-option parameter and open the input stream
         if (argv.length - startArgs > 0)
         {
            in = new FileInputStream (new File (argv[startArgs]));

            // tell the shell to return to System.in for input
            //   after the last input command -- this allows for a setup
            //   script to be run to get you in a known state so that
            //   you can test further
            continueWStdIn = true;
         }

         // get second non-option parameter and open the output stream
         if (argv.length - startArgs > 1)
         {
            out = new FileOutputStream (new File (argv[startArgs + 1]));

            // tell the shell to terminate after last input command
            continueWStdIn = false;
         }

         // load and run the shell with the default command bindings
         runShell (in, out, props, continueWStdIn, verbose, startDS);
      }
      catch (Throwable t)
      {
         System.out.println ("exception: " + t.toString ());

         if (verbose)
         {
            Debug.dumpException(t);
         }
      }

//      System.runFinalization ();
//      System.gc ();
      System.exit (0);
   }

   /**
    * Sets up a useful environment for SessionShell to operate in.
    *
    * <p>Binds the following commands to the shell: <i>load, unload, dir,
    * cd, cda, cds, cdcdef, cdadef, cdsdef, attr, ren, bind, rebind,
    * unbind, del, shell, exec, v, exit, quit, q, help, h, ?</i>.
    *
    * @param      in             (in) input stream from which to read
    *                            commands.
    * @param      out            (in) output stream to which to write output
    *                            (can be null for no output).
    * @param      props          (in) properties that contains any custom
    *                            values for the property
    *                            "java.naming.factory.initial" and other
    *                            properties needed by JNDI and the providers.
    * @param      continueWStdIn (in) true if control should be returned
    *                            to System.in after all commands have been
    *                            executed as read from the input stream.
    * @param      verbose        (in) true if stack dumps should be done
    *                            when exceptions occur.
    */
   public static void runShell (InputStream in, OutputStream out,
         Properties props, boolean continueWStdIn, boolean verbose,
         boolean startDS)
      throws SessionException
   {
      Debug.readDebugProperties(
         props.getProperty("debug.properties"));
      Debug.writeDebugProperties(
         props.getProperty("debug.properties"));
      SessionShell shell;
      SessionEnv env = new SessionEnv();
      env.modify(props);
      Boolean privateSM = new Boolean(props.getProperty("private"));
      SessionManager sm;
      if (true == privateSM.booleanValue())
         sm = SessionManagerFactory.getPrivate(env);
      else
         sm = SessionManagerFactory.getSessionManager(env);

      shell = new SessionShell (sm, in, out, continueWStdIn, verbose);

      // set up the default bindings
      //   because of load and unload, these can then be changed during
      //   the course of the execution of the shell
      shell.addCommand ("load",     new LoadCommand ());
      shell.addCommand ("unload",   new UnloadCommand ());
      shell.addCommand ("dir",      new DirCommand ());
      shell.addCommand ("cd",       new CdCommand ());
      shell.addCommand ("find",     new FindCommand ());
      shell.addCommand ("attr",     new AttrCommand ());
      shell.addCommand ("debug",    new DebugCommand ());
      shell.addCommand ("final",    new FinalizerCommand ());
      shell.addCommand ("trace",    new TraceCommand ());
      shell.addCommand ("close",    new CloseCommand ());
      shell.addCommand ("keep",     new KeepCommand ());
      shell.addCommand ("update",   new UpdateCommand ());
      shell.addCommand ("loging",   new LogingCommand ());
      shell.addCommand ("login",    new LoginCommand ());
      shell.addCommand ("logoutg",  new LogoutgCommand ());
      shell.addCommand ("logout",   new LogoutCommand ());
      shell.addCommand ("pause",    new PauseCommand ());

      shell.addCommand ("shell",    new ShellCommand ());
      shell.addCommand ("exec",     new ExecCommand ());
      shell.addCommand ("v",        new VerboseToggleCommand ());
      shell.addCommand ("exit",     new ExitCommand ());
      shell.addCommand ("quit",     new ExitCommand ());
      shell.addCommand ("q",        new ExitCommand ());
      shell.addCommand ("help",     new HelpCommand ());
      shell.addCommand ("h",        new HelpCommand ());
      shell.addCommand ("?",        new HelpCommand ());

      shell.run ();
   }
}

/**
 * Adds a command class to the shell under a command name.
 */
class LoadCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      if (argv.length < 2)
         throw new ShellException ("error: command name not specified");
      if (argv.length < 1)
         throw new ShellException ("error: command class not specified");
      if (argv.length > 2)
         throw new ShellException ("error: too many arguments");

      Object obj = null;

      // try to load the class specified in first parameter
      try
      {
         obj = Class.forName (argv[0]).newInstance ();
      }
      catch (ClassNotFoundException e)
      {
         throw new ShellException ("error: class not found: " + argv[0]);
      }
      catch (Throwable e)
      {
         throw new ShellException ("error: could not instantiate class: " +
               argv[0]);
      }

      // class must implement Command interface
      if (!(obj instanceof Command))
      {
         throw new ShellException ("error: class is not a Command: " +
               argv[0]);
      }

      // bind the command to the shell under the name in second parameter
      shell.addCommand (argv[1], (Command) obj);

      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println ("Command sucessfully loaded and bound to '" +
            argv[1] + "'");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c <cmdclass> <cmdname>",
         "  cmdname: overwrites any command already bound to shell",
         "Notes:",
         "- Adds a command to the shell under the specified command name.",
         "- The command class must implement the 'Command' interface.",
      };

      return (helpStrs);
   }
}

/**
 * Removes a command from the shell (can remove itself, so look out).
 */
class UnloadCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      if (argv.length < 1)
         throw new ShellException ("error: command name not specified");
      if (argv.length > 1)
         throw new ShellException ("error: too many arguments");

      Command oldCommand = shell.removeCommand (argv[0]);

      if (oldCommand == null)
         throw new ShellException ("error: no command bound to: " + argv[0]);

      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println ("Command sucessfully unbound");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c <cmdname>",
         "Notes:",
         "- Removes a command from the shell."
      };

      return (helpStrs);
   }
}

/**
 * Turns on/off debugging.
 */
class DebugCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      if (argv.length > 2)
         throw new ShellException ("error: too many arguments");

      // Change current setting
      if (1 <= argv.length)
      {
         if (argv[0].equalsIgnoreCase("on"))
            Debug.setDebug(true);
         else if (argv[0].equalsIgnoreCase("off"))
            Debug.setDebug(false);
      }

      // Change loudness
      if (2 <= argv.length)
      {
         Debug.setDebug(true);
         if (argv[1].equalsIgnoreCase("quiet"))
            Debug.setQuiet(true);
         if (argv[1].equalsIgnoreCase("loud"))
            Debug.setQuiet(false);
      }

      // Report setting
      if (!Debug.getDebug())
         out.println("Debug: off");
      else
         out.println("Debug: on, " + (Debug.getQuiet() ? "quiet" : "loud"));
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c [quiet|loud]",
         "  quiet: single-line debug messages",
         "  loud: include exception traces",
         "Notes:",
         "- Toggles debug mode and optionally sets quiet mode",
      };

      return (helpStrs);
   }
}

/**
 * Runs finalizers.
 */
class FinalizerCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      if (argv.length > 0)
         throw new ShellException ("error: too many arguments");

      System.runFinalization();

      out.println("Running finalizers...");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Runs finalization",
      };

      return (helpStrs);
   }
}

/**
 * Turns on/off instruction/method tracing.
 */
class TraceCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      Debug.traceInstructions(false);
      Debug.traceMethods(false);
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      if (argv.length > 1)
         throw new ShellException ("error: too many arguments");

      boolean traceInstructions = false;
      boolean traceMethods = false;
      if (argv.length > 0)
      {
         if (argv[0].toLowerCase().indexOf('i') != -1)
            traceInstructions = true;
         if (argv[0].toLowerCase().indexOf('m') != -1)
            traceMethods = true;
      }
      out.println(
         "TI: " + (traceInstructions ? "on" : "off") + " " +
         "TM: " + (traceMethods ? "on" : "off"));
      Debug.traceInstructions(traceInstructions);
      Debug.traceMethods(traceMethods);
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c [i|m]",
         "  i: instruction tracing on",
         "  m: method tracing on",
         "  : ",
         "Notes:",
         "- Turns on instruction and/or method tracing",
         "- If either param is missing, it is turned off",
      };

      return (helpStrs);
   }
}



/**
 * Lists sessions under the current session.
 */
class DirCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println("Dir:");

      Hashtable args = new Hashtable ();
      String path = "";
      String name = null;
      Vector attrIDVector = new Vector ();
      boolean dump = false;
      boolean refs = false;
      String refAttr[] = {Xplat.CONN_CONNECTION_REFERENCE_ATTR_ID};

      for (int i = 0; i < argv.length; i++)
      {
         String arg = argv[i];

         if (arg.startsWith ("["))
         {
/*
            arg = arg.trim ();

            if (!arg.endsWith ("]"))
            {
               throw new ShellException ("error: attr. ID list missing " +
                     "ending bracket");
            }

            arg = arg.substring (1, arg.length () - 1);

            int commaPos = 0;

            while (commaPos >= 0)
            {
               int savePos = commaPos;

               commaPos = arg.indexOf (',', commaPos);

               if (commaPos == -1)
                  attrIDVector.addElement (arg.substring (savePos));
               else
               {
                  attrIDVector.addElement (arg.substring (savePos,commaPos));
                  commaPos++;
               }
            }
            */
         }
         else if (arg.equals ("-dump"))
            dump = true;
         else if (arg.equals ("-refs"))
            refs = true;
         else if (name == null)
            name = arg;
         else
            throw new ShellException ("error: too many arguments");
      }

      String[] attrIDs;

      // flag whether a specific attribute set was requested
      if (attrIDVector.size () == 0)
         attrIDs = null;
      else
      {
         attrIDs = new String[attrIDVector.size ()];

         for (int i = 0; i < attrIDs.length; i++)
            attrIDs[i] = (String) attrIDVector.elementAt (i);
      }

      Session session = shell.getCurrSession();

      if (name != null)
      {
         session = session.getSession(name);
         out.println ("Name: " + name);
      }

/*
      SessionAttrs attrs;

      // a full attribute set is requested
      if (attrIDs == null)
         attrs = session.getAttributes ();
      // a specified attribute set is requested
      else
         attrs = session.getAttributes (attrIDs);

      if (attrs == null)
         throw new ShellException ("error: attribute set null for this " +
               "Session");
*/
//

      SessionAttrs as = new SessionAttrs();
      SessionEnumerator enum = shell.getCurrSession().search(as);
      while (enum.hasMoreElements())
      {
         Session found = enum.next();
         StringBuffer output = new StringBuffer(found.getDomainName());
         if (refs)
         {
            try
            {
               Integer ref =
                  (Integer)found.getAttributes(refAttr).getValue(refAttr[0]);
               if (null != ref)
                  output.append(" : " + getString(ref));
            }
            catch (SessionAttrNotFoundException e)
            {
               // Ignore
            }
         }
         out.println(output);
         if (dump)
         {
            SessionAttrs attrs = found.getAttributes ();
            // enumerate all attributes in the attribute set
            SessionAttrEnumerator enuma = attrs.getAttributes ();

            // print every attribute in the set
            while (enuma.hasMoreElements ())
            {
               SessionAttr a = enuma.next();
               out.println (" " + a.getSessionAttrId() +
                            " : " + getString(a.getValue()));
            }
         }
      }
   }

   private String getString(Object object)
   {
      if (object instanceof Integer)
      {
         int i = ((Integer)object).intValue();
         return "0x" + Integer.toHexString(i) + " (" + i + ")";
      }
      if (object instanceof Version)
      {
         Version v = (Version)object;
         return
            Long.toString (v.majorVersion) + "." +
            Long.toString (v.minorVersion) + "." +
            Long.toString (v.revision);
      }
      return object.toString();
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c [-refs] [-dump][[attrid,...]]",
         "  refs: print the requester's reference",
         "  dump: print attributes for each session",
         "  [attrid,...]: search criteria",
         "Notes:",
         "- List sessions below current session." };

      return (helpStrs);
   }
}

/**
 * Updates session links.
 */
class UpdateCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println("Update");
      shell.getCurrSession().validateLinks();
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c ",
         "Notes:",
         "- Updates session links." };

      return (helpStrs);
   }
}

/**
 * Changes the current session to a relative session.
 */
class CdCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

//      if (argv.length > 2)
//         throw new ShellException ("error: too many arguments");

      if (0 == argv.length)
      {
         Session sm = shell.getCurrSession();
         while (sm.hasParent())
            sm = sm.getParent();

         shell.setCurrSession(sm);

         shell.setPrompt (sm.getDomainName());
      }
      else
      {
         Session newSession;
         out.println("cd: " + argv[0]);
         newSession = shell.getCurrSession();
         if (argv[0].equals(".."))
         {
            if (newSession.hasParent())
               newSession = shell.getCurrSession().getParent();
         }
         else
         {
            SessionEnv env = new SessionEnv();

            if (argv.length > 1)
            {
               int type = Address.TYPE_WILD;
               int radix = 10;
               if (argv[1].equals("ipx"))
               {
                  type = Address.TYPE_IPX;
                  radix = 16;
               }
               else if (argv[1].equals("ddp"))
                  type = Address.TYPE_DDP;
               else if (argv[1].equals("asp"))
                  type = Address.TYPE_ASP;
               else if (argv[1].equals("udp"))
                  type = Address.TYPE_UDP;
               else if (argv[1].equals("tcp"))
                  type = Address.TYPE_TCP;
               else if (argv[1].equals("udp6"))
                  type = Address.TYPE_UDP6;
               else if (argv[1].equals("tcp6"))
                  type = Address.TYPE_TCP6;
               byte addr[] = new byte[argv.length-2];
               for (int i = 2; i < argv.length; i++)
               {
                  addr[i-2] = (byte)Short.parseShort(argv[i], radix);
               }
               Address address = new Address(type, addr);
               env.add(Xplat.DOMAIN_ADDRESS, address);
               out.println("...using: " + address);
            }

            if ('\\' == argv[0].charAt(0))
            {
               out.println("...from top");
               newSession = newSession.getSessionTop(argv[0].substring(1), env);
            }
            else
               newSession = newSession.getSession(argv[0], env);
         }

         shell.setCurrSession(newSession);
         shell.setPrompt(newSession.getDomainName());
      }
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c [[\\]domain [type address]]",
         "  domain: relative domain (getSession)'..' directives are allowed",
         "  \\domain: absolute domain (GetSessionTop)",
         "  type: ipx, ddp, asp, udp, tcp, udp6, tcp6",
         "  address: sequence of byte values for address",
         "Notes:",
         "- Changes current session to a session containing domain",
      };

      return (helpStrs);
   }
}

/**
 * Changes the current session to a relative session via findSession.
 */
class FindCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

//      if (argv.length > 2)
//         throw new ShellException ("error: too many arguments");

      if (0 == argv.length)
      {
         Session sm = shell.getCurrSession();
         while (sm.hasParent())
            sm = sm.getParent();

         shell.setCurrSession(sm);

         shell.setPrompt (sm.getDomainName());
      }
      else
      {
         Session newSession;
         out.println("cd: " + argv[0]);
         newSession = shell.getCurrSession();
         if (argv[0].equals(".."))
         {
            if (newSession.hasParent())
               newSession = shell.getCurrSession().getParent();
         }
         else
         {
            if ('\\' == argv[0].charAt(0))
            {
               out.println("...from top");
               newSession = newSession.findSessionTop(argv[0].substring(1));
            }
            else
               newSession = newSession.findSession(argv[0]);
         }

         if (null != newSession)
         {
            shell.setCurrSession(newSession);
            shell.setPrompt(newSession.getDomainName());
         }
         else
            out.println("Session not found.");
      }
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c [[\\]domain]",
         "  domain: relative domain (findSession)'..' directives are allowed",
         "  \\domain: absolute domain (findSessionTop)",
         "Notes:",
         "- Changes current session to a session containing domain name",
      };

      return (helpStrs);
   }
}

/**
 * Closes the current session.
 */
class CloseCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      if (argv.length > 0)
         throw new ShellException ("error: too many arguments");

      Session closingSession, currSession;
      currSession = closingSession = shell.getCurrSession();
      if (closingSession.hasParent())
      {
         currSession = closingSession.getParent();
         shell.setCurrSession(currSession);
      }

      shell.setPrompt (currSession.getDomainName());
      out.println("close");
      closingSession.close();
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Closes this session",
      };

      return (helpStrs);
   }
}

/**
 * Keeps the current session.
 */
class KeepCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      if (argv.length > 0)
         throw new ShellException ("error: too many arguments");

      Session currSession;
      currSession = shell.getCurrSession();
      PersistenceService s = (PersistenceService)currSession.getService(
         PersistenceService.KEY);
      out.println("keep");
      s.persist();
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Keeps this session at the system level",
      };

      return (helpStrs);
   }
}

/**
 * Dumps all or selected attributes of a Session.
 */
class AttrCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println("Attributes:");
      String name = null;
      Vector attrIDVector = new Vector ();
      boolean ids = false;
      boolean verbose = false;

      for (int i = 0; i < argv.length; i++)
      {
         String arg = argv[i];

         if (arg.startsWith ("["))
         {
            arg = arg.trim ();

            if (!arg.endsWith ("]"))
            {
               throw new ShellException ("error: attr. ID list missing " +
                     "ending bracket");
            }

            arg = arg.substring (1, arg.length () - 1);

            int commaPos = 0;

            while (commaPos >= 0)
            {
               int savePos = commaPos;

               commaPos = arg.indexOf (',', commaPos);

               if (commaPos == -1)
                  attrIDVector.addElement (arg.substring (savePos));
               else
               {
                  attrIDVector.addElement (arg.substring (savePos,commaPos));
                  commaPos++;
               }
            }
         }
         else if (arg.equals ("-ids"))
            ids = true;
         else if (arg.equals ("-verbose"))
            verbose = true;
         else if (name == null)
            name = arg;
         else
            throw new ShellException ("error: too many arguments");
      }

      String[] attrIDs;

      // flag whether a specific attribute set was requested
      if (attrIDVector.size () == 0)
         attrIDs = null;
      else
      {
         attrIDs = new String[attrIDVector.size ()];

         for (int i = 0; i < attrIDs.length; i++)
            attrIDs[i] = (String) attrIDVector.elementAt (i);
      }

      Session session = shell.getCurrSession();

      if (name != null)
      {
         session = session.getSession(name);
         out.println ("Name: " + name);
      }

      SessionAttrs attrs;

      // a full attribute set is requested
      if (attrIDs == null)
         attrs = session.getAttributes ();
      // a specified attribute set is requested
      else
         attrs = session.getAttributes (attrIDs);

      if (attrs == null)
         throw new ShellException ("error: attribute set null for this " +
               "Session");

      if (ids)
      {
         // enumerate all attributes in the attribute set
         Enumeration enum = attrs.getSessionAttrIds ();

         // print every attribute ID in the set
         while (enum.hasMoreElements ())
         {
            out.println (enum.nextElement() + ":");
         }
      }
      else
      {
         // enumerate all attributes in the attribute set
         SessionAttrEnumerator enum = attrs.getAttributes ();

         // print every attribute in the set
         while (enum.hasMoreElements ())
         {
            SessionAttr a = enum.next();
            out.println (a.getSessionAttrId() + " : " + getString(a.getValue()));
         }
      }

      // print diagnostic info on the actual attribute set
      if (verbose)
      {
         out.println ("verbose: size: " + attrs.count());
      }
   }

   private String getString(Object object)
   {
      if (object instanceof Integer)
      {
         int i = ((Integer)object).intValue();
         return "0x" + Integer.toHexString(i) + " (" + i + ")";
      }
      if (object instanceof Version)
      {
         Version v = (Version)object;
         return
            Long.toString (v.majorVersion) + "." +
            Long.toString (v.minorVersion) + "." +
            Long.toString (v.revision);
      }
      return object.toString();
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c [name] [-ids] [-verbose] [[attrid,...]] ",
         "  ids: print just the IDs returned by Attribute.getIDs()",
         "  verbose: print info on Attributes instance returned",
         "  [attrid,...]: specify limited set of string attr. IDs",
         "Notes:",
         "  Attribute returned by getAttributes() called with the",
         "  specified name."
      };

      return (helpStrs);
   }
}

/**
 * Attempts to login to the domain.
 */
class LoginCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println("Login:");

      if (argv.length > 2)
         throw new ShellException ("error: too many arguments");

      String userName = "";
      String password = "";
      if (argv.length > 0)
      {
         userName = argv[0];
         if (argv.length > 1)
            password = argv[1];
      }

      Session session = shell.getCurrSession();

      // Use authenticator
      if (session instanceof Authenticatable)
      {
         Identity id = session.createIdentity (userName);
         if (id instanceof PasswordIdentityFactory)
         {  // All we want is to have the instance be a password identity.
            //  We don't really need to use it for anything except to set
            //  the password. We know that anything that implements
            //  PasswordIdentity also implements Identity so we'll just
            //  recast it.
            id = (Identity) ((PasswordIdentityFactory) id).getPasswordIdentityInstance ();
            ((PasswordIdentity) id).setPassword (new Password (password));
         }
         Authenticator.login (id);

         out.println("successful");
      }
      else
         out.println("not available");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c user_name password",
         "  user name: user name to login as",
         "  password: password for user name",
         "Notes:",
         "- Attempts to login the session's domain by authenticating",
         "  the session via the authenticator.",
      };

      return (helpStrs);
   }
}

/**
 * Attempts to login to the domain.
 */
class LogingCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println("Login:");

      if (argv.length > 1)
         throw new ShellException ("error: too many arguments");

      String userName;
      if (argv.length > 0)
         userName = argv[0];
      else
         userName = "";

      Session session = shell.getCurrSession();

      // Use authenticator
      if (session instanceof Authenticatable)
      {
         Authenticator.login(session.createIdentity(userName));

         out.println("successful");
      }
      else
         out.println("not available");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c user_name",
         "  user name: user name to login as",
         "Notes:",
         "- Attempts to login the session's domain by authenticating",
         "  the session via the authenticator.",
      };

      return (helpStrs);
   }
}

/**
 * Pause the shell.
 */
class PauseCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);
//      PrintWriter out = new PrintWriter (System.out, true);
//      PrintWriter out = new PrintWriter (System.err, true);

      if (argv.length > 0)
         throw new ShellException ("error: too many arguments");

      out.println("Pausing...hit enter to continue");

      InputStreamReader in = new InputStreamReader(System.in);
      in.read();

      out.println("...done pausing.");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Pauses the shell",
      };

      return (helpStrs);
   }
}

/**
 * Attempts to logout of the domain.
 */
class LogoutCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println("Logout:");

      if (argv.length > 1)
         throw new ShellException ("error: too many arguments");

      Session session = shell.getCurrSession();

      // Use authenticator
      if (session instanceof Authenticatable)
      {
         Identity id = session.createIdentity ("");
         if (id instanceof PasswordIdentityFactory)
         {  // All we want is to have the instance be a password identity.
            //  We don't really need to use it for anything, we just want to
            //  avoid the authentication GUI. We know that anything that
            //  implements PasswordIdentity also implements Identity so
            //  we'll just recast it.
            id = (Identity) ((PasswordIdentityFactory) id).getPasswordIdentityInstance ();
         }
         Authenticator.logout(id);
//         Authenticatable s = session;
//         s.unauthenticate();
         out.println("successful");
      }
      else
         out.println("not available");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Attempts to logout from session's domain by unauthenticating",
         "  the session via the authenticator.",
      };

      return (helpStrs);
   }
}

/**
 * Attempts to logout of the domain.
 */
class LogoutgCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

      out.println("Logout:");

      if (argv.length > 1)
         throw new ShellException ("error: too many arguments");

      Session session = shell.getCurrSession();

      // Use authenticator
      if (session instanceof Authenticatable)
      {
         Authenticatable s = session;
         s.unauthenticate();
         out.println("successful");
      }
      else
         out.println("not available");
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Attempts to logout from session's domain by unauthenticating",
         "  the session via the authenticator.",
      };

      return (helpStrs);
   }
}

/**
 * Executes a shell script.
 */
class ShellCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      if (argv.length > 1)
         throw new ShellException ("error: too many arguments");
      if (argv.length < 1)
         throw new ShellException ("error: cmd. script must be specified");

      InputStream in = null;

      // open the shell script
      try
      {
         in = new FileInputStream (argv[0]);
      }
      catch (FileNotFoundException e)
      {
         throw new ShellException ("error: could not open file: " + argv[0]);
      }

      SessionShell child = new SessionShell (shell, null, in, null);

      child.run ();

      shell.clearCommands ();

      String[] cmdNames = child.getCommandNames ();

      for (int i = 0; i < cmdNames.length; i++)
         shell.addCommand (cmdNames[i], child.getCommand (cmdNames[i]));
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c <script>",
         "Notes:",
         "- Reads shell commands from the specified script file.",
         "- The currently executing shell's environment is duplicated and",
         "  passed to a child shell.",
         "- Output of child shell goes to output of current shell.",
         "- Parent shell's command table set to child's at exit."
      };

      return (helpStrs);
   }
}

/**
 * Executes an OS-specific command.
 */
class ExecCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      Process p = Runtime.getRuntime ().exec (argv);
      PrintWriter out = new PrintWriter (shell.getOutputStream (), true);
      int exitCode = p.waitFor ();

      out.println ("External process exited: " + exitCode);
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c <cmd>",
         "Notes:",
         "- All parameters passed to this command are passed to the local",
         "  OS as a command line parameter."
      };

      return (helpStrs);
   }
}

/**
 * Toggles the verbose flag in the shell.
 */
class VerboseToggleCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      if (argv.length > 0)
      {
         PrintWriter out = new PrintWriter (shell.getOutputStream (), true);

         out.println ("error: too many arguments");
      }

      shell.setVerbose (!shell.getVerbose ());
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Toggles the verbose flag in the current shell."
      };

      return (helpStrs);
   }
}

/**
 * Exits the shell.
 */
class ExitCommand implements CommandWithHelp
{
   public void execute (SessionShell shell, String[] argv)
      throws Exception
   {
      throw new ShellException (true);
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c",
         "Notes:",
         "- Exits the current shell; any parent shell resumes control."
      };

      return (helpStrs);
   }
}

/**
 * Prints help for all of the default bound commands.
 *
 * <p>This help will not be accurate if the command bindings are changed.
 */
class HelpCommand implements CommandWithHelp
{
   String substituteCommandName (String cmdName, String syntax)
   {
      int cmdPos;

      if ((cmdPos = syntax.indexOf ("%c")) != -1)
      {
         return (syntax.substring (0, cmdPos) + cmdName +
               syntax.substring (cmdPos + 2));
      }
      else
         return (syntax);
   }

   void sortStringArr (String[] arr)
   {
      for (int i = arr.length; i > 0; i--)
      {
         for (int j = 0; j < i; j++)
         {
            if (arr[i-1].compareTo (arr[j]) < 0)
            {
               String temp = arr[j];

               arr[j] = arr[i-1];
               arr[i-1] = temp;
            }
         }
      }
   }

   public void execute (SessionShell s, String[] argv)
      throws Exception
   {
      if (argv.length > 1)
         throw new ShellException ("error: too many arguments");

      PrintWriter out = new PrintWriter (s.getOutputStream (), true);

      if (argv.length == 0)
      {
         out.println (shell.helpStr);

         String[] cmdNames = s.getCommandNames ();
         String[] helpStrs;
         Command cmd;

         sortStringArr (cmdNames);

         for (int i = 0; i < cmdNames.length; i++)
         {
            out.print (cmdNames[i] + ": ");
            cmd = s.getCommand (cmdNames[i]);

            if (cmd instanceof CommandWithHelp)
            {
               CommandWithHelp hcmd = (CommandWithHelp) cmd;
               helpStrs = hcmd.getHelpStrs ();

               if (helpStrs.length > 0)
               {
                  out.print (substituteCommandName (cmdNames[i],
                        helpStrs[0]));
               }
            }
            else
            {
               out.print ("no help available (" +
                     cmd.getClass ().getName () + ")");
            }

            out.println ();
         }
      }
      else if (argv.length == 1)
      {
         Command cmd = s.getCommand (argv[0]);

         if (cmd == null)
            throw new ShellException ("error: command undefined: "+ argv[0]);
         if (!(cmd instanceof CommandWithHelp))
            throw new ShellException ("error: command does not support " +
                  "help: " + argv[0]);

         CommandWithHelp hcmd = (CommandWithHelp) cmd;
         String[] helpStrs = hcmd.getHelpStrs();

         for (int i = 0; i < helpStrs.length; i++)
            out.println (substituteCommandName (argv[0], helpStrs[i]));
      }
   }

   public String[] getHelpStrs ()
   {
      String[] helpStrs =
      {
         "Syntax: %c [cmdname]",
         "  cmdname: name of command for which to display help",
         "Notes:",
         "- If executed with no parameters, displays syntax of all commands."
      };

      return (helpStrs);
   }
}
