image
 
image
CommandLine.java


/*::.
==================================================================================================================================
=================================================¦ Copyright © 2007 Allen Baker ¦=================================================
                                                 +------------------------------+
File:       CommandLine.java
Originator: Allen Baker (2007.08.15 21:32)
LayoutRev:  5
================================================================================================================================== */



/*.
==========================================================================================
Package
------------------------------------------------------------------------------------------ */
package cosmicabyss.com.lib;



/*.
==========================================================================================
Imports
------------------------------------------------------------------------------------------ */
import java.io.*;
import java.util.*;



/*::
======================================================================================================================== *//**
Instances of this class are for parsing and making sense of the command line arguments (tokens) passed into a program,
similar to the GNU getopt() command line parser. Tokens may be placed in any order on the command line.
   <BLOCKQUOTE>
      Caveat: A token representing a control that allows an optional argument should not immediately precede a token
              Representing a non-control unless the token representing the non-control is intended to be the argument
              for the token representing the control.
   </BLOCKQUOTE>

Instances of this class support command line options that begin with a single dash. Unlike GNU getopt(), instances of
this class handle long (multi-character) controls just like single character ones, without requiring any special syntax.
They therefore do not need or support command line options that begin with a double dash. They do not support multiple
option characters being packed together into a single string preceded by a single dash (multiple options specified
together ) as such a construct would be indistinguishable from a long (multi-character) control preceded by a single
dash (which instances of this class do support).<P>

   <B>
      Background
   </B>
From <A href="http://en.wikipedia.org/wiki/Getopt" target="_blank">Wikipedia
</A>
   <BLOCKQUOTE>
      A long standing issue with command line programs was how to specify options; early programs used many ways of
      doing so, including:
         <BLOCKQUOTE>
            => single character options (-a),<BR>
            => multiple options specified together (-abc is equivalent to -a -b -c),<BR>
            => multi-character options (-inum),<BR>
            => options with arguments (-a arg, -inum 3, -a=arg),<BR>
            => and different prefix characters (-a, +b, /c).
         </BLOCKQUOTE>

      The getopt() function was written to be a standard mechanism that all programs could use to parse command-line
      options so that there would be a common interface that everyone could depend on. As such, the original authors
      picked out of the variations support for single character options, multiple options specified together, and
      options with arguments (-a arg or -aarg), all controllable by an option string.<P>

      Getopt() dates back to at least 1980 and was first published by AT&T at the 1985 UNIFORUM conference in Dallas,
      Texas. Versions of it were subsequently picked up by other flavors of Unix (BSD 4.3, GNU/Linux, etc.). It is
      specified in the POSIX.2 standard. Derivatives of getopt() have been created for many programming languages to
      parse command-line options.<P>

      A GNU extension, getopt()_long, allows parsing of more readable, multi-character options, which are introduced by
      two dashes instead of one. The choice of two dashes allows multi-character options (--inum) to be differentiated
      from single character options specified together (-abc). The GNU extension also allows an alternative format for
      options with arguments: --name=arg.
   </BLOCKQUOTE>

Of the ways to specify options mentioned above, instances of this class support the following:
   <BLOCKQUOTE>
      => single character options (-a),<BR>
      => multi-character options preceded by a single dash (-inum),<BR>
      => options with arguments and no equals sign (-a arg, -inum 3);
   </BLOCKQUOTE>

And do not support the following:
   <BLOCKQUOTE>
      => multiple options specified together (-abc is equivalent to -a -b -c),<BR>
      => multi-character options preceded by a double dash (--inum)<BR>
      => options with arguments and an equals sign (-a=arg),<BR>
      => different prefix characters (+b, /c).<BR>
      => alternative format for options with arguments: --name=arg.
   </BLOCKQUOTE>

Additionally, instances of this class support the following:
   <BLOCKQUOTE>
      => arguments separated from their option character by whitespace (-a arg),<BR>
      => arguments not separated from their option character by whitespace (-aarg),<BR>
      => arguments inside of quotes (-a "arg", -a"arg").
   </BLOCKQUOTE>

Instances of this class support the following types of command line tokens:
   <BLOCKQUOTE>
      => optional options that may or may not be included on the command line,<BR>
      => mandatory options that must be included on the command line,<BR>
      => options that cannot include an argument,<BR>
      => options that may or may not include an optional argument,<BR>
      => options that must include a mandatory argument,<BR>
      => file pathname specifiers (with or without wildcard characters).
         <BLOCKQUOTE>
            -> All tokens on the command line without a beginning dash are interpreted as being either the argument for
               An Immediately preceding option or as a file pathname specifier. If the token immediately preceding is an
               option requiring an argument or is an option that accepts an optional argument, then the token without a
               beginning dash is interpreted as the argument to the option. If the token immediately preceding is not an
               option or is an option that does not accept an argument, then the token without a beginning dash is
               interpreted as a file pathname specifier.<P>

            Obviously, if a preceding token is one that requires or accepts an argument and it already has an argument
            associated with it, then the following token without a beginning dash is not considered the preceding
            option's argument or any part of it. It is considered a file pathname specifier.
         </BLOCKQUOTE>
   </BLOCKQUOTE>

Tokens are separated from each other on the command line by any number of consecutive whitespace characters (not
including an end of line, of course).<P>

To group a series of whitespace separated words or strings together into one option argument or file pathname specifier,
the group must be enclosed in double quotes. For example, to group these words:
   <BLOCKQUOTE>
      Life's short, code hard!
   </BLOCKQUOTE>

together as the argument for this option:
   <BLOCKQUOTE>
      -advice
   </BLOCKQUOTE>

group them and place the group next to the option as follows:
   <BLOCKQUOTE>
      -advice "Life's short, code hard!"
   </BLOCKQUOTE>

 or
   <BLOCKQUOTE>
      -advice"Life's short, code hard!"
   </BLOCKQUOTE>

Any string argument or file pathname specifier containing any BLANK or DASH characters must be enclosed in quotes on
the commandline.<P>

To use the DASH character ('-') as the first character in a string, "escape it" by placing the AT character ('@') just
in front of the DASH. DASH characters after the first character in a string do not need to be escaped.
   <BLOCKQUOTE>
      <PRE id="unindent">
         String on cmdline    Is interpreted as    Bacause
         =================    =================    =======
         "-some string"       -s "ome string"      leading dash not escaped, dash and first character
                                                   interpreted as option
         "@-some string"      "-some string"       leading dash escaped, the AT character is striped
         "@-some-string"      "-some-string"       non-leading dash doesn't need to be escaped
         "@-some@-string"     "-some@-string"      non-leading dash can't be escaped
      </PRE>
   </BLOCKQUOTE>

<P>
   <DL>
      <DT>
         <B>
            Example usage:
         </B>
         <DD>
            <BLOCKQUOTE>
               <PRE id="unindent">
                  =========================================================================================
                  this cannot be done statically because it throws an exception.
                  -----------------------------------------------------------------------------------------
                  iGlobalProperties = new GlobalProperties();

                  =========================================================================================
                  get ready to process the command line arguments
                  -----------------------------------------------------------------------------------------
                  CommandLine  cmdLn = new CommandLine(CLASS_NAME,pArgs);

                  =========================================================================================
                  these are the valid command line controls
                  -----------------------------------------------------------------------------------------
                  cmdLn.addControlsFromString(":hqrsl::x:");

                  =========================================================================================
                  list the valid controls
                  -----------------------------------------------------------------------------------------
                  cmdLn.listControls();
                  cOut.println();

                  =========================================================================================
                  interpret the arguments on the command line
                  -----------------------------------------------------------------------------------------
                  HashSet<XString>          controlsWithoutArgs = new HashSet<XString>();
                  HashMap<XString,XString>  controlsWithArgs    = new HashMap<XString,XString>();
                  ArrayList<XString>        excludedFileSpecs   = new ArrayList<XString>();
                  ArrayList<XString>        includedFileSpecs   = new ArrayList<XString>();
                  boolean                   result              = cmdLn.commandLineArgs
                    (
                    iGlobalProperties  ,
                    controlsWithoutArgs,
                    controlsWithArgs   ,
                    excludedFileSpecs  ,
                    includedFileSpecs
                    );

                  =========================================================================================
                  if the command line was messed up, abort the program
                  -----------------------------------------------------------------------------------------
                  if (includedFileSpecs.isEmpty())
                    {
                    cOut.println("COMMAND LINE ERROR: MANDATORY control NOT on command line:  fileSpecs");
                    }
                  if ((result == false) || (includedFileSpecs.isEmpty()))
                    {
                    usage();
                    cOut.titledSeverityPrintf
                       (
                       Const.HALT,
                       CLASS_NAME,
                       "Command line error."
                       );
                    cOut.stopLog();
                    System.exit(-1);
                    }

                  =========================================================================================
                  arguments for the FileNameList with their default values.  Some of
                  these arguments may be modified by command line controls.
                  -----------------------------------------------------------------------------------------
                  boolean  recurse             = false;
                  boolean  fullyQualifiedNames = true;
                  boolean  matchSubdirs        = false;
                  boolean  sort                = true;
                  boolean  collapse            = false;

                  =========================================================================================
                  process command line controls without arguments
                  -----------------------------------------------------------------------------------------
                  for (XString  ctl : controlsWithoutArgs)
                    {
                    cOut.println(ctl.string() + " control is present.");
                    if (ctl.equals("-h"))
                       {
                       usage();
                       cOut.stopLog();
                       System.exit(-1);
                       }
                    recurse      = ctl.equals("-r");
                    matchSubdirs = ctl.equals("-s");
                    }
                  if (controlsWithoutArgs.size() > 0) cOut.println();

                  =========================================================================================
                  process command line controls with arguments
                  -----------------------------------------------------------------------------------------
                  Set<XString>  set = controlsWithArgs.keySet();
                  for (XString  ctl : set)
                    {
                    cOut.println(ctl.string() + " control is present with the argument: " + controlsWithArgs.get(ctl));
                    }
                  if (controlsWithArgs.size() > 0) cOut.println();

                  =========================================================================================
                  get a list of all the files names that will be processed
                  -----------------------------------------------------------------------------------------
                  for (XString  fileSpec : includedFileSpecs)
                    {
                    cOut.println("Files matching this fileSpec will be INcluded: " + fileSpec);
                    }
                  if (includedFileSpecs.size() > 0) cOut.println();
                  for (XString  fileSpec : excludedFileSpecs)
                    {
                    cOut.println("Files matching this fileSpec will be EXcluded: " + fileSpec);
                    }
                  if (excludedFileSpecs.size() > 0) cOut.println();
                  iFileNameList = new FileNameList
                    (
                    includedFileSpecs,excludedFileSpecs,recurse,fullyQualifiedNames,matchSubdirs,sort,collapse
                    );
               </PRE>
            </BLOCKQUOTE>
         </DD>
      </DT>
      <DT>
         <B>
            View Source:
         </B>
         <DD>
            <A href="CommandLine.java.html">
               CommandLine.java
            </A>
         </DD>
      </DT>
      <DT>
         <B>
            Author:
         </B>
         <DD>
            <A href="mailto:sourcecode.v01@cosmicabyss.com">
               Allen Baker
            </A>
         </DD>
      </DT>
   </DL>
*//*
======================================================================================================================== */
public class CommandLine
   {



   /*:                                    :METHOD:000:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#000">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type1,Type2> CommandLine(Type1 pProgramName, Type2[] pArgv)
      {
      iArgv        = XString.toXStringList(pArgv);
      iProgramName = new XString(pProgramName);
      }



   /*:                                    :METHOD:001:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows the user to add controls one at a time. A control that does not permit an
   argument should set pArgRequirement to "none". A control that allows an optional argument should set
   pArgRequirement to "optional". A control that requires an argument should set pArgRequirement to
   "required".

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#001">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> CommandLine addControl(Type pCtlName, int pArgRequirement)
      {
      iControls.add(new Control(pCtlName,pArgRequirement));
      return this;
      }



   /*:                                    :METHOD:002:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows the user to add controls one at a time. A control that does not permit an
   argument should set pArgRequirement to "none". A control that allows an optional argument should set
   pArgRequirement to "optional". A control that requires an argument should set pArgRequirement to
   "required".

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#002">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> CommandLine addControl(Type pCtlName, int pArgRequirement, boolean pMandatory)
      {
      iControls.add(new Control(pCtlName,pArgRequirement,pMandatory));
      return this;
      }



   /*:                                    :METHOD:003:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows the user to add controls one at a time. A control that does not permit an
   argument should set pArgRequirement to "none". A control that allows an optional argument should set
   pArgRequirement to "optional". A control that requires an argument should set pArgRequirement to
   "required".

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#003">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type1,Type2> CommandLine addControl(Type1 pCtlName, Type2 pArgRequirement)
      {
      NCString  reqName = new NCString(pArgRequirement);
      int       reqNum  = 0;
      if (reqName.equals("optional")) reqNum = 1;
      if (reqName.equals("required")) reqNum = 2;
      iControls.add(new Control(pCtlName,reqNum));
      return this;
      }



   /*:                                    :METHOD:004:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows the user to add controls one at a time. A control that does not permit an
   argument should set pArgRequirement to "none". A control that allows an optional argument should set
   pArgRequirement to "optional". A control that requires an argument should set pArgRequirement to
   "required".

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#004">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type1,Type2> CommandLine addControl(Type1 pCtlName, Type2 pArgRequirement, boolean pMandatory)
      {
      NCString  reqName = new NCString(pArgRequirement);
      int       reqNum  = 0;
      if (reqName.equals("optional")) reqNum = 1;
      if (reqName.equals("required")) reqNum = 2;
      iControls.add(new Control(pCtlName,reqNum,pMandatory));
      return this;
      }



   /*:                                    :METHOD:005:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows the user to add several controls with one call using a GNU getopt compatible
   controls string.<P>

   The first char in the control string should be a ":". A control letter that REQUIRES an argument is
   followed by a ":" a control letter that ALLOWS AN OPTIONAL argument is followed by a "::"
   <BLOCKQUOTE>
      <PRE id="unindent">
         Here's an example control string:  ":hqrsl::t:c:ijw"

         Here's a list of the tokens that this method would extract from that control string along
         with how it interprets each one:
            Token    Signals that
            =====    ===============================================================================
            :        This token is the mandatory leading colon
            h        Option -h is allowed on the command line, and it MUST NOT have an argument.
            q        Option -q is allowed on the command line, and it MUST NOT have an argument.
            r        Option -r is allowed on the command line, and it MUST NOT have an argument.
            s        Option -s is allowed on the command line, and it MUST NOT have an argument.
            l::      Option -l is allowed on the command line, and it CAN HAVE an OPTIONAL argument.
            t:       Option -t is allowed on the command line, and it MUST HAVE an argument.
            c:       Option -c is allowed on the command line, and it MUST HAVE an argument.
            i        Option -i is allowed on the command line, and it MUST NOT have an argument.
            j        Option -j is allowed on the command line, and it MUST NOT have an argument.
            w        Option -w is allowed on the command line, and it MUST NOT have an argument.
      </PRE>
   </BLOCKQUOTE>

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#005">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> CommandLine addControlsFromString(Type pCtlString)
      {
      /*.
      ==========================================================================================
      Print a message telling the user what's going on
      ------------------------------------------------------------------------------------------ */
      cOut.println
         (
         "Setting valid command line controls from this string:  \"" +
         UString.toString(pCtlString)                                +
         "\""
         );
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      char[]  ctls       = UString.toString(pCtlString).toCharArray();
      char    firstChar  = 0;
      char    secondChar = 0;
      char    thirdChar  = 0;
      int     argReq     = 0;
      /*.
      ==========================================================================================
      Systems that are case sensitive should use case sensitive controls
      ------------------------------------------------------------------------------------------ */
      XString  control = Util.fileNamesAreCaseSensitive()? new XString("-") : new NCString("-");
      /*.
      ==========================================================================================
      Scan the string picking out each control and interpreting its requirement for arguments.
      ------------------------------------------------------------------------------------------ */
      for (int i=0, j=1, k=2; i<ctls.length; i++, j++, k++)
         {
         firstChar  = ctls[i];
         secondChar = (j<ctls.length)? ctls[j] : '\0';
         thirdChar  = (k<ctls.length)? ctls[k] : '\0';
         if (firstChar != ':')
            {
            argReq = 0;
            if ((secondChar == ':') &&     (thirdChar == ':') ) argReq = 1;
            if ((secondChar == ':') && ( ! (thirdChar == ':'))) argReq = 2;
            iControls.add(new Control(control.concat(firstChar),argReq));
            }
         }
      return this;
      }



   /*:                                    :METHOD:006:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method prints a list of the valid controls

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#006">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void listControls() throws Exception
      {
      for (Control  control : iControls)
         {
         XString  ctlName   = control.name();
         XString  argRqmnt  = null;
         XString  mandatory = new XString(control.isMandatory()? "  -- MANDATORY CONTROL --" : "");
         switch (control.argReq())
            {
            case 0:
               argRqmnt = new XString("PROHIBITS an argument");
               break;
            case 1:
               argRqmnt = new XString("ALLOWS    an argument");
               break;
            default:
               argRqmnt = new XString("REQUIRES  an argument");
               break;
            }
         if (ctlName instanceof NCString)
            {
            cOut.printf("Control NOT case sensitive and %s:  \"%s\"%s\n",argRqmnt,ctlName,mandatory);
            }
         else
            {
            cOut.printf("Control IS  case sensitive and %s:  \"%s\"%s\n",argRqmnt,ctlName,mandatory);
            }
         }
      }



   /*:                                    :METHOD:007:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This allows a user to make a control mandatory

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#007">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void makeControlMandatory(Type pCtlName)
      {
      XString  token = new XString(pCtlName);
      for (Control  control : iControls)
         {
         XString  ctlName  = control.name();
         if (tokenHit(token,ctlName))
            {
            control.setMandatory(true);
            break;
            }
         }
      }



   /*:                                    :METHOD:008:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This allows a user to make a control non-mandatory

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#008">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void makeControlNonMandatory(Type pCtlName)
      {
      XString  token = new XString(pCtlName);
      for (Control  control : iControls)
         {
         XString  ctlName  = control.name();
         if (tokenHit(token,ctlName))
            {
            control.setMandatory(false);
            break;
            }
         }
      }



   /*:                                    :METHOD:009:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method checks what's on the command line to see if it's correct and returns the command line
   contents to the calling method.

   Note:
      This is the legacy version of this method. It's the version that most applications use. It DOES
      NOT ALLOW multiple intances of the same option on the command line. That is a side effect of it
      being passed a HashMap to hold the controls that have arguments.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#009">View source</A>

   @return
      True if the command line is properly formed; false otherwise.

   @param
      pGlbProp is the GlobalProperties object where global properties are set.
   @param
      pControlWithoutArgs is a hash set into which controls without arguments are placed so the calling
      program can access them.
   @param
      pControlWithArgs is a hash map into which controls with arguments are placed, along with their
      arguments so the calling program can access them. The map is keyed with the control.
   @param
      pExcludedFileSpecs is a list into which filespecs that identify files that should NOT be
      processed are placed.
   @param
      pIncludedFileSpecs is a list into which filespecs that identify files that SHOULD be processed
      are placed.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public boolean commandLineArgs
      (
      GlobalProperties          pGlbProp,
      HashSet<XString>          pControlsWithoutArgs,
      HashMap<XString,XString>  pControlsWithArgs,
      ArrayList<XString>        pExcludedFileSpecs,
      ArrayList<XString>        pIncludedFileSpecs
      )
      throws Exception
      {
      /*.
      ==========================================================================================
      True, indicating success, is the default return code. If any errors are found, the return
      code will be set to false.
      ------------------------------------------------------------------------------------------ */
      boolean  correct = true;
      /*.
      ==========================================================================================
      Get whatever was on the command line and process it against the valid control set.
      ------------------------------------------------------------------------------------------ */
      processCmdLn();
      /*.
      ==========================================================================================
      Disposition each control or file spec found on the command line
      ------------------------------------------------------------------------------------------ */
      for (XString ctl = nextCtl(); ctl != null; ctl = nextCtl())
         {
         XString  arg = ctlArg();
         /*.
         ==========================================================================================
         A control that required an argument was on the command line but its argument was missing
         ------------------------------------------------------------------------------------------ */
         if (ctl.equals(":"))
            {
            cOut.println("COMMAND LINE ERROR: " + arg.string());
            correct = false;
            }
         /*.
         ==========================================================================================
         An invalid control was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("?"))
            {
            cOut.println("COMMAND LINE ERROR: " + arg.string());
            correct = false;
            }
         /*.
         ==========================================================================================
         A mandatory control was missing on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("!"))
            {
            cOut.println("COMMAND LINE ERROR: " + arg.string());
            correct = false;
            }
         /*.
         ==========================================================================================
         An included filespec was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("+"))
            {
            pIncludedFileSpecs.add(arg);
            }
         /*.
         ==========================================================================================
         A file containing included filespecs was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-ifile"))
            {
            XStringsIterator  tIter = new XStringsIterator(arg);
            while (tIter.hasNext())
               {
               XString  filespec = tIter.next().detab(3).trim();
               if ( ! filespec.equals(""))
                  {
                  pIncludedFileSpecs.add(filespec);
                  }
               }
            }
         /*.
         ==========================================================================================
         An excluded filespec was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-x"))
            {
            pExcludedFileSpecs.add(arg);
            }
         /*.
         ==========================================================================================
         A file containing excluded filespecs was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-xfile"))
            {
            XStringsIterator  tIter = new XStringsIterator(arg);
            while (tIter.hasNext())
               {
               XString  filespec = tIter.next().detab(3).trim();
               if ( ! filespec.equals(""))
                  {
                  pExcludedFileSpecs.add(filespec);
                  }
               }
            }
         /*.
         ==========================================================================================
         The -q control, if it is present, is processed in here instead of being returned to the
         caller for processing.

            Since messages can be sent to the message log from this method, if "quiet" has been
            asked for on the command line then we need to shut up right now instead of waiting
            until after this method returns so that any messages generated in this method are not
            printed.

         The println() will show in the log file only if -q comes after -l on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-q"))
            {
            cOut.setQuiet(true);
            cOut.println("-q control is present.");
            }
         /*.
         ==========================================================================================
         The -l control, if it is present, is processed in here instead of being returned to the
         caller for processing.

            The -l control has a optional argument.

            Since messages can be sent to the message log from this method, if a log file has been
            asked for on the command line then we need to get it open right now instead of waiting
            until after this method returns so that any messages generated in this method will be
            captured in the log file.
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-l"))
            {
            boolean  noArg = arg.equals("");
            if (noArg)
               {
               /*.
               ==========================================================================================
               No log file was specified on the command line so generate a default log file name and act
               like it was on the command line.
               ------------------------------------------------------------------------------------------ */
               arg = new XString
                  (
                  Util.timeStamp("(yyyy.MM.dd.HH.mm.ss)").string() +
                  "."                                              +
                  UString.toString(iProgramName)                   +
                  ".LOG"
                  );
               }
            /*.
            ==========================================================================================
            Create the log file and tell the logging object to start using it.
            ------------------------------------------------------------------------------------------ */
            File  logFile = new File(pGlbProp.getProperty(Const.PROP_LOG_DIRECTORY).string(),arg.string());
            arg = new XString(logFile.getCanonicalPath());
            cOut.startLog(arg);
            /*.
            ==========================================================================================
            Tell the user what's going on here.
            ------------------------------------------------------------------------------------------ */
            cOut.println("Logging output to: " + arg);
            cOut.println();
            if (noArg)
               {
               cOut.println("-l control is present.");
               }
            else
               {
               cOut.println("-l control is present with the argument: " + arg);
               }
            }
         /*.
         ==========================================================================================
         If it's not any of the above controls, then it's either a valid control without an
         argument or a valid control with an argument, so put it in the right place.
         ------------------------------------------------------------------------------------------ */
         else
            {
            if (arg.equals(""))
               {
               pControlsWithoutArgs.add(ctl);
               }
            else
               {
               pControlsWithArgs.put(ctl,arg);
               }
            }
         }
      /*.
      ==========================================================================================
      If the command line was messed up, then return a failure code for this method; otherwise,
      return a success code.
      ------------------------------------------------------------------------------------------ */
      return correct;
      }



   /*:                                    :METHOD:010:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method checks what's on the command line to see if it's correct and returns the command line
   contents to the calling method.

   Note:
      This is the new version of this method. It DOES ALLOW multiple intances of the same option on the
      command line. That is a side effect of it being passed two ArrayLists to hold the controls that
      have arguments and their arguments respectively.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#010">View source</A>

   @return
      True if the command line is properly formed; false otherwise.

   @param
      pGlbProp is the GlobalProperties object where global properties are set.
   @param
      pControlWithoutArgs is a hash set into which controls without arguments are placed so the calling
      program can access them.
   @param
      pControlsWithArgs is a list into which controls are placed with a one-to-one correspondance to the
      arguments i pArgs.
   @param
      pArgs is a list into which arguments are placed with a one-to-one correspondance to the controls
      in pControlsWithArgs.
   @param
      pExcludedFileSpecs is a list into which filespecs that identify files that should NOT be
      processed are placed.
   @param
      pIncludedFileSpecs is a list into which filespecs that identify files that SHOULD be processed
      are placed.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public boolean commandLineArgs
      (
      GlobalProperties    pGlbProp,
      HashSet<XString>    pControlsWithoutArgs,
      ArrayList<XString>  pControlsWithArgs,
      ArrayList<XString>  pArgs,
      ArrayList<XString>  pExcludedFileSpecs,
      ArrayList<XString>  pIncludedFileSpecs
      )
      throws Exception
      {
      /*.
      ==========================================================================================
      True, indicating success, is the default return code. If any errors are found, the return
      code will be set to false.
      ------------------------------------------------------------------------------------------ */
      boolean  correct = true;
      /*.
      ==========================================================================================
      Get whatever was on the command line and process it against the valid control set.
      ------------------------------------------------------------------------------------------ */
      processCmdLn();
      /*.
      ==========================================================================================
      Disposition each control or file spec found on the command line
      ------------------------------------------------------------------------------------------ */
      for (XString ctl = nextCtl(); ctl != null; ctl = nextCtl())
         {
         XString  arg = ctlArg();
         /*.
         ==========================================================================================
         A control that required an argument was on the command line but its argument was missing
         ------------------------------------------------------------------------------------------ */
         if (ctl.equals(":"))
            {
            cOut.println("COMMAND LINE ERROR: " + arg.string());
            correct = false;
            }
         /*.
         ==========================================================================================
         An invalid control was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("?"))
            {
            cOut.println("COMMAND LINE ERROR: " + arg.string());
            correct = false;
            }
         /*.
         ==========================================================================================
         A mandatory control was missing on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("!"))
            {
            cOut.println("COMMAND LINE ERROR: " + arg.string());
            correct = false;
            }
         /*.
         ==========================================================================================
         An included filespec was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("+"))
            {
            pIncludedFileSpecs.add(arg);
            }
         /*.
         ==========================================================================================
         A file containing included filespecs was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-ifile"))
            {
            XStringsIterator  tIter = new XStringsIterator(arg);
            while (tIter.hasNext())
               {
               XString  filespec = tIter.next().detab(3).trim();
               if ( ! filespec.equals(""))
                  {
                  pIncludedFileSpecs.add(filespec);
                  }
               }
            }
         /*.
         ==========================================================================================
         An excluded filespec was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-x"))
            {
            pExcludedFileSpecs.add(arg);
            }
         /*.
         ==========================================================================================
         A file containing excluded filespecs was on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-xfile"))
            {
            XStringsIterator  tIter = new XStringsIterator(arg);
            while (tIter.hasNext())
               {
               XString  filespec = tIter.next().detab(3).trim();
               if ( ! filespec.equals(""))
                  {
                  pExcludedFileSpecs.add(filespec);
                  }
               }
            }
         /*.
         ==========================================================================================
         The -q control, if it is present, is processed in here instead of being returned to the
         caller for processing.

            Since messages can be sent to the message log from this method, if "quiet" has been
            asked for on the command line then we need to shut up right now instead of waiting
            until after this method returns so that any messages generated in this method are not
            printed.

         The println() will show in the log file only if -q comes after -l on the command line
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-q"))
            {
            cOut.setQuiet(true);
            cOut.println("-q control is present.");
            }
         /*.
         ==========================================================================================
         The -l control, if it is present, is processed in here instead of being returned to the
         caller for processing.

            The -l control has a optional argument.

            Since messages can be sent to the message log from this method, if a log file has been
            asked for on the command line then we need to get it open right now instead of waiting
            until after this method returns so that any messages generated in this method will be
            captured in the log file.
         ------------------------------------------------------------------------------------------ */
         else if (ctl.equals("-l"))
            {
            boolean  noArg = arg.equals("");
            if (noArg)
               {
               /*.
               ==========================================================================================
               No log file was specified on the command line so generate a default log file name and act
               like it was on the command line.
               ------------------------------------------------------------------------------------------ */
               arg = new XString
                  (
                  Util.timeStamp("(yyyy.MM.dd.HH.mm.ss)").string() +
                  "."                                              +
                  UString.toString(iProgramName)                   +
                  ".LOG"
                  );
               }
            /*.
            ==========================================================================================
            Create the log file and tell the logging object to start using it.
            ------------------------------------------------------------------------------------------ */
            File  logFile = new File(pGlbProp.getProperty(Const.PROP_LOG_DIRECTORY).string(),arg.string());
            arg = new XString(logFile.getCanonicalPath());
            cOut.startLog(arg);
            /*.
            ==========================================================================================
            Tell the user what's going on here.
            ------------------------------------------------------------------------------------------ */
            cOut.println("Logging output to: " + arg);
            cOut.println();
            if (noArg)
               {
               cOut.println("-l control is present.");
               }
            else
               {
               cOut.println("-l control is present with the argument: " + arg);
               }
            }
         /*.
         ==========================================================================================
         If it's not any of the above controls, then it's either a valid control without an
         argument or a valid control with an argument, so put it in the right place.
         ------------------------------------------------------------------------------------------ */
         else
            {
            if (arg.equals(""))
               {
               pControlsWithoutArgs.add(ctl);
               }
            else
               {
               pControlsWithArgs.add(ctl);
               pArgs.add(arg);
               }
            }
         }
      /*.
      ==========================================================================================
      If the command line was messed up, then return a failure code for this method; otherwise,
      return a success code.
      ------------------------------------------------------------------------------------------ */
      return correct;
      }



   /*:                                    :METHOD:011:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the ConsoleStream for this object.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#011">View source</A>

   @return
      A reference to this object

   @param
      pConsole is a ConsoleStream through which this objects sends its output.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public CommandLine setOut(ConsoleStream pConsole)
      {
      cOut = pConsole;
      return this;
      }



   /*:                                    :METHOD:012:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This boilerplate method is used for testing an instantiated object of this class and may include any
   code the developer chooses.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#012">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public CommandLine test() throws Exception
      {
      cOut.titledPrintf
         (
         "\"HELLO WORLD!\"",
         "%s  %s  %s",
         "I'm an object of the", CLASS_NAME, "class, and I approved this message."
         );
      return this;
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Protected  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Private  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*.
   ==========================================================================================
   Class Constants
      CLASS_NAME:
         The name of this class DFLT_LINE_LEN : the default line length for word wrapping
   ------------------------------------------------------------------------------------------ */
   private static final XString  CLASS_NAME    = new XString(CommandLine.class.getName());
   private static final int      DFLT_LINE_LEN = ConsoleMessage.defaultLineLength();
   /*.
   ==========================================================================================
   Class variables
      cOut:
         Console output.
   ------------------------------------------------------------------------------------------ */
   private static ConsoleStream  cOut = ConsoleStream.getSingleton();
   /*.
   ==========================================================================================
   Instance variables
      iProgramName:
         The name of the program that is being run from the Command line (the program using
         this object)
      iArgv:
         An ArrayList containing the argument strings as they were entered on the command
         line and passed into the main() method that invoked the program.
      iNextCmdLnCtl:
         Used as an iterator that synchronizes access to iCmdLnCtls and iCmdLnArgs.
      iNumCmdLnCtls:
         A count of how many items are in iCmdLnCtls/iCmdLnArgs
      iCmdLnCtls:
         an ordered list of the controls that were found on the command line
      iCmdLnArgs :
         An ordered list of the arguments to the controls that were found on the command
         line. The position in the list of each argument corresponds to the position of its
         control in the controls list.
      iControls :
         A list of all the allowed controls and their Properties.
   ------------------------------------------------------------------------------------------ */
   private XString             iProgramName  = null;
   private ArrayList<XString>  iArgv         = null;
   private int                 iNextCmdLnCtl = (-1);
   private int                 iNumCmdLnCtls = (-1);
   private ArrayList<XString>  iCmdLnCtls    = new ArrayList<XString>();
   private ArrayList<XString>  iCmdLnArgs    = new ArrayList<XString>();
   private ArrayList<Control>  iControls     = new ArrayList<Control>();



   /*:                                    :METHOD:013:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method parses and validates the command line arguments

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#013">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private void processCmdLn()
      {
      ListOfLists<XString> ctlsAndArgs = null;
      /*.
      ==========================================================================================
      This is done so that when the list of valid controls is scanned looking for a match to a
      token, the longest matching one is found first. For example, if "-a" and "-ab" are both
      valid controls, then the token "-abc" would be interpreted as "-ab" with and argument of
      "c" instead of "-a" with an argument of "bc".
      ------------------------------------------------------------------------------------------ */
      sortControlsByLength();
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      ctlsAndArgs   = this.parsedCmdLn();
      iCmdLnCtls    = ctlsAndArgs.get(0);
      iCmdLnArgs    = ctlsAndArgs.get(1);
      iNumCmdLnCtls = iCmdLnCtls.size();
      iNextCmdLnCtl = (-1);
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      ctlsAndArgs   = this.validatedCtlsAndArgs();
      iCmdLnCtls    = ctlsAndArgs.get(0);
      iCmdLnArgs    = ctlsAndArgs.get(1);
      iNumCmdLnCtls = iCmdLnCtls.size();
      iNextCmdLnCtl = (-1);
      }



   /*:                                    :METHOD:014:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sorts the valid controls list in LENGTH-DESCENDING order because it is scanned for
   matches sequentially from the beginning of the array. When the list of valid controls is scanned
   looking for a match to a token, the longest matching one is found first. For example, if "-a" and
   "-ab" are both valid controls, then the token "-abc" would be interpreted as "-ab" with and argument
   of "c" instead of "-a" with an argument of "bc".

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#014">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private void sortControlsByLength()
      {
      Control[]  ctls = new Control[iControls.size()];
      ctls = iControls.toArray(ctls);
      Arrays.sort(ctls);
      iControls = new ArrayList<Control>(Arrays.asList(ctls));
      }



   /*:                                    :METHOD:015:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method is like an iterator because it gets the next control from the list and then advances a
   pointer to point to the next control in the list, which it will return on the next call. It returns
   null when it gets to the end of the list and then it wraps back around to the beginning for the next
   call.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#015">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private XString nextCtl()
      {
      iNextCmdLnCtl++;
      if (iNextCmdLnCtl < iNumCmdLnCtls)
         {
         return iCmdLnCtls.get(iNextCmdLnCtl);
         }
      iNextCmdLnCtl = (-1);
      return null;
      }



   /*:                                    :METHOD:016:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method gets the argument corresponding to the last control returned by nextCtl().

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#016">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private XString ctlArg()
      {
      if (iNextCmdLnCtl < iNumCmdLnCtls)
         {
         return iCmdLnArgs.get(iNextCmdLnCtl);
         }
      return null;
      }



   /*:                                    :METHOD:017:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method scans the control list to see if the specified control allows or requires an argument.
   If so, it returns true, otherwise it returns false.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#017">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private boolean allowsArgument(XString pControl)
      {
      for (Control control : iControls)
         {
         XString ctlName = control.name();
         boolean allowed = (control.argReq() > 0);
         if (tokenHit(pControl,ctlName))
            {
            return allowed;
            }
         }
      return false;
      }



   /*:                                    :METHOD:018:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method compares a token with a valid control to see if there is a match.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#018">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private boolean tokenHit(XString pTok, XString pCtl)
      {
      return (pCtl instanceof NCString)? pTok.startsWithIgnoreCase(pCtl) : pTok.startsWith(pCtl);
      }



   /*:                                    :METHOD:019:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   Some tokens on the command line are a combination of a control and its argument. GNU getopt allows a
   control to be separated by white space from its argument but does not require it. This method finds
   the ones where the control and argument are part of the same token and it separates them into two
   tokens.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#019">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private ArrayList<XString> separateCtlsFromArgs(ArrayList<XString> pIn)
      {
      ArrayList<XString> out = new ArrayList<XString>();
      for (XString token : pIn)
         {
         if (token.startsWith("-"))
            {
            boolean  hit = false;
            for (Control  control : iControls)
               {
               XString  ctlName = control.name();
               if (tokenHit(token,ctlName))
                  {
                  hit = true;
                  if (ctlName.length() < token.length())
                     {
                     out.add(token.substring(0,ctlName.length()));
                     out.add(token.substring(ctlName.length(),token.length()));
                     }
                  else
                     {
                     out.add(token);
                     }
                  break;
                  }
               }
            if ( ! hit)
               {
               out.add(token);
               }
            }
         else
            {
            out.add(token);
            }
         }
      return out;
      }



   /*:                                    :METHOD:020:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method takes the raw arguments from the command line and makes sense of them. It returns two
   arraylists, one containing the controls found on the command line and the other containing the
   corresponding argument for each control

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#020">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private ListOfLists<XString> parsedCmdLn()
      {
      /*.
      ==========================================================================================
      Make a local copy of the command line to use so that it can be reused if the user for some
      reason chooses to add or change some controls after processing the command line and then
      reprocesses it.
      ------------------------------------------------------------------------------------------ */
      ArrayList<XString> cmdLnTokens = XString.toXStringList(iArgv);
      cmdLnTokens = separateCtlsFromArgs(cmdLnTokens);
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      ArrayList<XString> ctls = new ArrayList<XString>();
      ArrayList<XString> args = new ArrayList<XString>();
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      ListOfLists<XString> ctlsAndArgs = new ListOfLists<XString>();
      ctlsAndArgs.add(ctls);
      ctlsAndArgs.add(args);
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      XString  thisToken = null;
      XString  nextToken = null;
      int      i    = 0;
      while (i<cmdLnTokens.size())
         {
         /*.
         ==========================================================================================
         The next token becomes "this" token and the one following it becomes the "next" token
         ------------------------------------------------------------------------------------------ */
         thisToken = cmdLnTokens.get(i);
         nextToken = ((i+1) < cmdLnTokens.size())? cmdLnTokens.get(i+1) : new XString("");
         /*.
         ==========================================================================================
         If this token is a filespec:
            <BLOCKQUOTE>
               <PRE id="unindent">
                  1) create and add "+" (which means "add this filespec") as the next control and
                     then add the actual filespec as the control's argument.
                  2) Then skip over this token to point to the next token
               </PRE>
            </BLOCKQUOTE>
         ------------------------------------------------------------------------------------------ */
         if ( ! thisToken.startsWith("-"))
            {
            ctls.add(new XString("+"));
            /*.
            ==========================================================================================
            if the token starts with an escaped DASH, remove the CARET.
            ------------------------------------------------------------------------------------------ */
            if (thisToken.startsWith("@-"))
               {
               thisToken = thisToken.trimLeft("@");
               }
            args.add(thisToken);
            i++;
            }
         /*.
         ==========================================================================================
         Otherwise, this token is a command line control
         ------------------------------------------------------------------------------------------ */
         else
            {
            /*.
            ==========================================================================================
            If the token following this one on the command line is also a control then this control
            has no argument:
               <BLOCKQUOTE>
                  <PRE id="unindent">
                     1) add this token as the next control
                     2) add an empty string as the control's argument
                     3) Then skip over this token to point to the next token
                  </PRE>
               </BLOCKQUOTE>
            ------------------------------------------------------------------------------------------ */
            if (nextToken.startsWith("-"))
               {
               ctls.add(thisToken);
               args.add(new XString(""));
               i++;
               }
            /*.
            ==========================================================================================
            Otherwise, the token following this one on the command line is not a control so it could
            be either a filespec or the argument to this control token.
               <BLOCKQUOTE>
                  <PRE id="unindent">
                     1) if this token is a control that requires or allows an Argument, then the next
                        token must be treated as its argument.
                     2) if this token is a control that does not take an Argument, then the next token
                        will be left to be handled as a filespec on the next iteration of this loop.
                  </PRE>
               </BLOCKQUOTE>
            ------------------------------------------------------------------------------------------ */
            else
               {
               /*.
               ==========================================================================================
               If this token is a control that requires or allows an argument, the next token must be
               treated as its argument.
                  <BLOCKQUOTE>
                     <PRE id="unindent">
                        1) add this token as the next control
                        2) add the next token as the control's argument
                        3) Then skip over both tokens to point to the next token
                     </PRE>
                  </BLOCKQUOTE>
               ------------------------------------------------------------------------------------------ */
               if (allowsArgument(thisToken))
                  {
                  ctls.add(thisToken);
                  /*.
                  ==========================================================================================
                  if the token starts with an escaped DASH, remove the CARET.
                  ------------------------------------------------------------------------------------------ */
                  if (nextToken.startsWith("@-"))
                     {
                     nextToken = nextToken.trimLeft("@");
                     }
                  args.add(nextToken);
                  i++;
                  i++;
                  }
               /*.
               ==========================================================================================
               Otherwise, this token is a control that does not take an argument, so the next token will
               be left to be handled as a filespec on the next iteration of this loop.
                  <BLOCKQUOTE>
                     <PRE id="unindent">
                        1) add this token as the next control
                        2) add an empty string as the control's argument
                        3) Then skip over this token to point to the next token
                     </PRE>
                  </BLOCKQUOTE>
               ------------------------------------------------------------------------------------------ */
               else
                  {
                  ctls.add(thisToken);
                  args.add(new XString(""));
                  i++;
                  }
               }
            }
         }
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      return ctlsAndArgs;
      }



   /*:                                    :METHOD:021:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method scans the valid controls list for the first control that is a match for the token. It
   returns the matching control or null if one is not found.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#021">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private Control matchingControl(XString pToken)
      {
      for (Control  control : iControls)
         {
         XString ctlName = control.name();
         if (tokenHit(pToken,ctlName))
            {
            return control;
            }
         }
      return null;
      }



   /*:                                    :METHOD:022:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method goes through the list of controls and arguments found on the command line and validates
   each one. If one is found in the list that is not valid, the control is replaced with an error
   indicator and its argument is replaced with an error message.

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#022">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private ListOfLists<XString> validatedCtlsAndArgs()
      {
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      ArrayList<XString> ctls = new ArrayList<XString>();
      ArrayList<XString> args = new ArrayList<XString>();
      /*.
      ==========================================================================================
      ------------------------------------------------------------------------------------------ */
      ListOfLists<XString> ctlsAndArgs = new ListOfLists<XString>();
      ctlsAndArgs.add(ctls);
      ctlsAndArgs.add(args);
      /*.
      ==========================================================================================
      Look for valid and invalid controls on the command line. A valid control is rendered
      invalid if it requires an argument and there isn't one.
      ------------------------------------------------------------------------------------------ */
      for (XString ctl = nextCtl(); ctl != null; ctl = nextCtl())
         {
         XString  arg = ctlArg();

         if (ctl.equals("+"))
            {
            ctls.add(ctl);
            args.add(arg);
            }
         else
            {
            Control  control = matchingControl(ctl);
            if (control == null)
               {
               ctls.add(new XString("?"));
               if (arg.equals(""))
                  {
                  args.add(new XString("found INVALID control on command line:  " + ctl.string()));
                  }
               else
                  {
                  args.add(new XString("found INVALID control on command line:  " + ctl.string() + "   with this argument:  " + arg.string()));
                  }
               }
            else
               {
               switch (control.argReq())
                  {
                  case 0:
                     if (arg.equals(""))
                        {
                        ctls.add(control.name());
                        args.add(arg);
                        }
                     else
                        {
                        ctls.add(new XString("?"));
                        args.add(new XString("found VALID   control on command line:  " + ctl.string() + "   which should not have this or any other argument:  " + arg.string()));
                        }
                     break;
                  case 1:
                     ctls.add(control.name());
                     args.add(arg);
                     break;
                  default:
                     if ( ! arg.equals(""))
                        {
                        ctls.add(control.name());
                        args.add(arg);
                        }
                     else
                        {
                        ctls.add(new XString(":"));
                        args.add(new XString("found VALID   control on command line:  " + ctl.string() + "   missing its required argument"));
                        }
                     break;
                  }
               }
            }
         }
      /*.
      ==========================================================================================
      Look for any mandatory controls that are not on the command line
      ------------------------------------------------------------------------------------------ */
      for (Control control : iControls)
         {
         if (control.isMandatory())
            {
            boolean  found = false;
            XString  name  = control.name();
            iNextCmdLnCtl  = (-1);
            for (XString token = nextCtl(); token != null; token = nextCtl())
               {
               found = tokenHit(token,name);
               if (found) break;
               }
            if ( ! found)
               {
               ctls.add(new XString("!"));
               args.add(new XString("MANDATORY control NOT on command line:  " + name.string()));
               }
            }
         }
      /*.
      ==========================================================================================
      Return the revised list of ctls and args
      ------------------------------------------------------------------------------------------ */
      return ctlsAndArgs;
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Inner Classes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   private class Control implements Comparable<Control>
      {
      public <Type> Control(Type pCtlName, int pArgRequirement)
         {
         iCtlName        = (pCtlName instanceof NCString)? new NCString(pCtlName) : new XString(pCtlName);
         iArgRequirement = pArgRequirement;
         }
      public <Type> Control(Type pCtlName, int pArgRequirement, boolean pMandatory)
         {
         iCtlName        = (pCtlName instanceof NCString)? new NCString(pCtlName) : new XString(pCtlName);
         iArgRequirement = pArgRequirement;
         iMandatory      = pMandatory;
         }
      public int compareTo(Control c2)
         {
         /*.
         ==========================================================================================
         Causes sorting into ascending order of length
         ------------------------------------------------------------------------------------------ */
         return c2.name().length() - this.name().length();
         }
      public XString name()
         {
         return iCtlName;
         }
      public int argReq()
         {
         return iArgRequirement;
         }
      public boolean isMandatory()
         {
         return iMandatory;
         }
      public void setMandatory(boolean pMandatory)
         {
         iMandatory = pMandatory;
         }
      private XString  iCtlName        = new XString("");
      private int      iArgRequirement = 0;  // 0=no, 1=optional, 2=manadatory
      private boolean  iMandatory      = false;
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Public Static Methods  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:                                    :METHOD:023:BOOKMARK:
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows this class file to be unit tested as a standalone application. It's the method
   that's called when the class is invoked from the command line by using the java application launcher
   - "java". Main() is not a required method, but the practice of putting one in each class and
   wrapping class test code within it allows easy unit testing of the class; and main does not need to
   be removed when testing is complete.

   <P>
      <DL>
         <DT>
            <B>
               Command line usage:
            </B>
            <DD>
               Java cosmicabyss.com.lib.CommandLine
            </DD>
         </DT>
      </DL>

   <P><B>Implementation: </B><A HREF="CommandLine.java.html#023">View source</A>

   @param
      pArgs contains the command line arguments with which this class was invoked as an application.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public static void main(String[] pArgs) throws Exception
      {
      XString programName = new XString("CommandLine");
      /*.
      ==========================================================================================
      Greetings !
      ------------------------------------------------------------------------------------------ */
      cOut.banner(CLASS_NAME);
      /*.
      ==========================================================================================
      Create an object and send its output to the ConsoleStream
      ------------------------------------------------------------------------------------------ */
      CommandLine  obj = new CommandLine(programName,pArgs);
      /*.
      ==========================================================================================
      Test code
      ------------------------------------------------------------------------------------------ */
      obj.test();
      /*.
      ==========================================================================================
      These are the valid command line controls
      ------------------------------------------------------------------------------------------ */
      obj.addControlsFromString(":hqrsl::t:c:x:");
//      obj.addControl(new NCString("-h"), 0);
//      obj.addControl(new NCString("-q"), 0);
//      obj.addControl(new NCString("-r"), 0);
//      obj.addControl(new NCString("-s"), 0);
//      obj.addControl(new NCString("-l"), 1);
//      obj.addControl(new NCString("-t"), 2);
//      obj.addControl(new NCString("-c"), 2);
//      obj.addControl(new NCString("-x"), 2);
      obj.makeControlMandatory("-c");
      /*.
      ==========================================================================================
      List the valid controls
      ------------------------------------------------------------------------------------------ */
      obj.listControls();
      cOut.println();
      /*.
      ==========================================================================================
      Interpret the arguments on the command line
      ------------------------------------------------------------------------------------------ */
      GlobalProperties          glbProp             = new GlobalProperties();
      HashSet<XString>          controlsWithoutArgs = new HashSet<XString>();
      HashMap<XString,XString>  controlsWithArgs    = new HashMap<XString,XString>();
      ArrayList<XString>        excludedFileSpecs   = new ArrayList<XString>();
      ArrayList<XString>        includedFileSpecs   = new ArrayList<XString>();
      boolean                   result              = obj.commandLineArgs
         (
         glbProp            ,
         controlsWithoutArgs,
         controlsWithArgs   ,
         excludedFileSpecs  ,
         includedFileSpecs
         );
      /*.
      ==========================================================================================
      If the command line was messed up, abort the program
      ------------------------------------------------------------------------------------------ */
      if (result == false)
         {
         cOut.titledSeverityPrintf
            (
            Const.HALT,
            programName,
            "Command line error."
            );
         System.exit(-1);
         }
      /*.
      ==========================================================================================
      Arguments for the FileNameList with their default values.
      ------------------------------------------------------------------------------------------ */
      boolean  recurse             = false;
      boolean  fullyQualifiedNames = false;
      boolean  matchSubdirs        = false;
      boolean  sort                = true;
      boolean  collapse            = false;
      /*.
      ==========================================================================================
      Process command line controls without arguments
      ------------------------------------------------------------------------------------------ */
      for (XString  ctl : controlsWithoutArgs)
         {
         cOut.println(ctl.string() + " control is present.");
         if      (ctl.equals("-r")) recurse      = true;
         else if (ctl.equals("-s")) matchSubdirs = true;
         }
      /*.
      ==========================================================================================
      Process command line controls with arguments
      ------------------------------------------------------------------------------------------ */
      Set<XString>  set = controlsWithArgs.keySet();
      for (XString  ctl : set)
         {
         cOut.println(ctl.string() + " control is present with the argument: " + controlsWithArgs.get(ctl));
         }
      /*.
      ==========================================================================================
      Process command line filespecs
      ------------------------------------------------------------------------------------------ */
      for (XString  fileSpec : includedFileSpecs)
         {
         cOut.println("Files matching this fileSpec will be INcluded: " + fileSpec);
         }
      for (XString  fileSpec : excludedFileSpecs)
         {
         cOut.println("Files matching this fileSpec will be EXcluded: " + fileSpec);
         }
      cOut.println();
      FileNameList  fileNames = new FileNameList
         (
         includedFileSpecs,excludedFileSpecs,recurse,fullyQualifiedNames,matchSubdirs,sort,collapse
         );
      /*.
      ==========================================================================================
      For each file name in the resulting list ...
      ------------------------------------------------------------------------------------------ */
      for (XString  name : fileNames)
         {
         TextFile  file = new TextFile(name);
         cOut.println(file.getCanonicalPath());
         }
      /*.
      ==========================================================================================
      All went well, now we're done
      ------------------------------------------------------------------------------------------ */
      cOut.println(programName.string() + " completed successfully.");
      cOut.stopLog();



      }



   }  // class CommandLine



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Notes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */