Home | History | Annotate | Download | only in args
      1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
      2  *
      3  * This program and the accompanying materials are made available under
      4  * the terms of the Common Public License v1.0 which accompanies this distribution,
      5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
      6  *
      7  * $Id: OptsParser.java,v 1.1.1.1 2004/05/09 16:57:57 vlad_r Exp $
      8  */
      9 package com.vladium.util.args;
     10 
     11 import java.io.CharArrayWriter;
     12 import java.io.IOException;
     13 import java.io.InputStream;
     14 import java.io.InputStreamReader;
     15 import java.io.PrintWriter;
     16 import java.io.Reader;
     17 import java.util.ArrayList;
     18 import java.util.HashMap;
     19 import java.util.HashSet;
     20 import java.util.Iterator;
     21 import java.util.List;
     22 import java.util.Map;
     23 import java.util.Set;
     24 
     25 import com.vladium.util.IConstants;
     26 import com.vladium.util.ResourceLoader;
     27 
     28 // ----------------------------------------------------------------------------
     29 /**
     30  * @author Vlad Roubtsov, (C) 2002
     31  */
     32 final class OptsParser implements IOptsParser
     33 {
     34     // public: ................................................................
     35 
     36     // TODO: #-comments
     37     // TODO: prefixing for error messages
     38     // TODO: support var subst (main class name, etc)
     39     // TODO: support short/full usage
     40     // TODO: support marking opts as for displayable in full usage only
     41 
     42     public synchronized void usage (final PrintWriter out, final int level, final int width)
     43     {
     44         // TODO: use width
     45         // TODO: cache?
     46 
     47         final String prefix = OPT_PREFIXES [CANONICAL_OPT_PREFIX];
     48 
     49         for (Iterator i = m_metadata.getOptDefs (); i.hasNext (); )
     50         {
     51             final OptDef optdef = (OptDef) i.next ();
     52 
     53             if ((level < 2) && optdef.isDetailedOnly ()) // skip detailed usage only options
     54                 continue;
     55 
     56             final StringBuffer line = new StringBuffer ("  ");
     57 
     58             final String canonicalName = optdef.getCanonicalName ();
     59             final boolean isPattern = optdef.isPattern ();
     60 
     61             line.append (prefix);
     62             line.append (canonicalName);
     63             if (isPattern) line.append ('*');
     64 
     65             final String [] names = optdef.getNames ();
     66             for (int n = 0; n < names.length; ++ n)
     67             {
     68                 final String name = names [n];
     69                 if (! name.equals (canonicalName))
     70                 {
     71                     line.append (", ");
     72 
     73                     line.append (prefix);
     74                     line.append (name);
     75                     if (isPattern) line.append ('*');
     76                 }
     77             }
     78 
     79             final String vmnemonic = optdef.getValueMnemonic ();
     80             if (vmnemonic != null)
     81             {
     82                 line.append (' ');
     83                 line.append (vmnemonic);
     84             }
     85 
     86 
     87             int padding = 16 - line.length ();
     88             if (padding < 2)
     89             {
     90                 // end the current line
     91                 out.println (line);
     92 
     93                 line.setLength (0);
     94                 for (int p = 0; p < 16; ++ p) line.append (' ');
     95             }
     96             else
     97             {
     98                 for (int p = 0; p < padding; ++ p) line.append (' ');
     99             }
    100 
    101             if (optdef.isRequired ()) line.append ("{required} ");
    102             line.append (optdef.getDescription ());
    103 
    104             out.println (line);
    105         }
    106 
    107         if (level < DETAILED_USAGE)
    108         {
    109             final OptDef usageOptDef = m_metadata.getUsageOptDef ();
    110             if ((usageOptDef != null) && (usageOptDef.getNames () != null) && (usageOptDef.getNames ().length > 1))
    111             {
    112                 out.println ();
    113                 out.println ("  {use '" + usageOptDef.getNames () [1] + "' option to see detailed usage help}");
    114             }
    115         }
    116     }
    117 
    118     public synchronized IOpts parse (final String [] args)
    119     {
    120         if (args == null) throw new IllegalArgumentException ("null input: args");
    121 
    122         final Opts opts = new Opts ();
    123 
    124         {
    125             final String [] nv = new String [2]; // out buffer for getOptNameAndValue()
    126             final String [] pp = new String [1]; // out buffer for getOptDef()
    127 
    128             // running state/current vars:
    129             int state = STATE_OPT;
    130             OptDef optdef = null;
    131             Opt opt = null;
    132             String value = null;
    133             int valueCount = 0;
    134 
    135             int a;
    136       scan: for (a = 0; a < args.length; )
    137             {
    138                 final String av = args [a];
    139                 if (av == null) throw new IllegalArgumentException ("null input: args[" + a + "]");
    140 
    141                 //System.out.println ("[state: " + state + "] av = " + av);
    142 
    143                 switch (state)
    144                 {
    145                     case STATE_OPT:
    146                     {
    147                         if (isOpt (av, valueCount, optdef))
    148                         {
    149                             // 'av' looks like an option: get its name and see if it
    150                             // is in the metadata
    151 
    152                             valueCount = 0;
    153 
    154                             getOptNameAndValue (av, nv); // this can leave nv[1] as null
    155 
    156                             // [assertion: nv [0] != null]
    157 
    158                             final String optName = nv [0]; // is not necessarily canonical
    159                             optdef = m_metadata.getOptDef (optName, pp); // pp [0] is always set by this
    160 
    161                             if (optdef == null)
    162                             {
    163                                 // unknown option:
    164 
    165                                 // TODO: coded messages?
    166                                 opts.addError (formatMessage ("unknown option \'" + optName + "\'"));
    167 
    168                                 state = STATE_ERROR;
    169                             }
    170                             else
    171                             {
    172                                 // merge if necessary:
    173 
    174                                 final String canonicalName = getOptCanonicalName (optName, optdef);
    175                                 final String patternPrefix = pp [0];
    176 
    177                                 opt = opts.getOpt (canonicalName);
    178 
    179                                 if (optdef.isMergeable ())
    180                                 {
    181                                     if (opt == null)
    182                                     {
    183                                         opt = new Opt (optName, canonicalName, patternPrefix);
    184                                         opts.addOpt (opt, optdef, optName);
    185                                     }
    186                                 }
    187                                 else
    188                                 {
    189                                     if (opt == null)
    190                                     {
    191                                         opt = new Opt (optName, canonicalName, patternPrefix);
    192                                         opts.addOpt (opt, optdef, optName);
    193                                     }
    194                                     else
    195                                     {
    196                                         opts.addError (formatMessage ("option \'" + optName + "\' cannot be specified more than once"));
    197 
    198                                         state = STATE_ERROR;
    199                                     }
    200                                 }
    201 
    202                                 value = nv [1];
    203 
    204                                 if (value == null) ++ a;
    205                                 state = STATE_OPT_VALUE;
    206                             }
    207                         }
    208                         else
    209                         {
    210                             // not in STATE_OPT_VALUE and 'av' does not look
    211                             // like an option: the rest of args are free
    212 
    213                             state = STATE_FREE_ARGS;
    214                         }
    215                     }
    216                     break;
    217 
    218 
    219                     case STATE_OPT_VALUE:
    220                     {
    221                         // [assertion: opt != null and optdef != null]
    222 
    223                         if (value != null)
    224                         {
    225                             // value specified explicitly using the <name>separator<value> syntax:
    226                             // [don't shift a]
    227 
    228                             valueCount = 1;
    229 
    230                             final int [] cardinality = optdef.getValueCardinality ();
    231 
    232                             if (cardinality [1] < 1)
    233                             {
    234                                 opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept values: \'" + value + "\'"));
    235 
    236                                 state = STATE_ERROR;
    237                             }
    238                             else
    239                             {
    240                                 ++ a;
    241                                 opt.addValue (value);
    242                             }
    243                         }
    244                         else
    245                         {
    246                             value = args [a];
    247 
    248                             final int [] cardinality = optdef.getValueCardinality ();
    249 
    250                             if (isOpt (value, valueCount, optdef))
    251                             {
    252                                 if (valueCount < cardinality [0])
    253                                 {
    254                                     opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
    255 
    256                                     state = STATE_ERROR;
    257                                 }
    258                                 else
    259                                     state = STATE_OPT;
    260                             }
    261                             else
    262                             {
    263                                 if (valueCount < cardinality [1])
    264                                 {
    265                                     ++ valueCount;
    266                                     ++ a;
    267                                     opt.addValue (value);
    268                                 }
    269                                 else
    270                                 {
    271                                     // this check is redundant:
    272 //                                    if (valueCount < cardinality [0])
    273 //                                    {
    274 //                                        opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
    275 //
    276 //                                        state = STATE_ERROR;
    277 //                                    }
    278 //                                    else
    279                                         state = STATE_FREE_ARGS;
    280                                 }
    281                             }
    282                         }
    283 
    284                         value = null;
    285                     }
    286                     break;
    287 
    288 
    289                     case STATE_FREE_ARGS:
    290                     {
    291                         if (isOpt (args [a], valueCount, optdef))
    292                         {
    293                             state = STATE_OPT;
    294                         }
    295                         else
    296                         {
    297                             opts.setFreeArgs (args, a);
    298                             break scan;
    299                         }
    300                     }
    301                     break;
    302 
    303 
    304                     case STATE_ERROR:
    305                     {
    306                         break scan; // TODO: could use the current value of 'a' for a better error message
    307                     }
    308 
    309                 } // end of switch
    310             }
    311 
    312             if (a == args.length)
    313             {
    314                 if (opt != null) // validate the last option's min cardinality
    315                 {
    316                     final int [] cardinality = optdef.getValueCardinality ();
    317 
    318                     if (valueCount < cardinality [0])
    319                     {
    320                         opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
    321                     }
    322                 }
    323                 else
    324                 {
    325                     opts.setFreeArgs (args, a);
    326                 }
    327             }
    328 
    329         } // end of 'args' parsing
    330 
    331 
    332         final IOpt [] specified = opts.getOpts ();
    333         if (specified != null)
    334         {
    335             // validation: all required parameters must be specified
    336 
    337             final Set /* String(canonical name) */ required = new HashSet ();
    338             required.addAll (m_metadata.getRequiredOpts ());
    339 
    340             for (int s = 0; s < specified.length; ++ s)
    341             {
    342                 required.remove (specified [s].getCanonicalName ());
    343             }
    344 
    345             if (! required.isEmpty ())
    346             {
    347                 for (Iterator i = required.iterator (); i.hasNext (); )
    348                 {
    349                     opts.addError (formatMessage ("missing required option \'" + (String) i.next () + "\'"));
    350                 }
    351             }
    352 
    353             for (int s = 0; s < specified.length; ++ s)
    354             {
    355                 final IOpt opt = specified [s];
    356                 final OptDef optdef = m_metadata.getOptDef (opt.getCanonicalName (), null);
    357 
    358 //                // validation: value cardinality constraints
    359 //
    360 //                final int [] cardinality = optdef.getValueCardinality ();
    361 //                if (opt.getValueCount () < cardinality [0])
    362 //                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' must have at least " + cardinality [0] +  " value(s)"));
    363 //                else if (opt.getValueCount () > cardinality [1])
    364 //                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' must not have more than " + cardinality [1] +  " value(s)"));
    365 
    366                 // validation: "requires" constraints
    367 
    368                 final String [] requires = optdef.getRequiresSet (); // not canonicalized
    369                 if (requires != null)
    370                 {
    371                     for (int r = 0; r < requires.length; ++ r)
    372                     {
    373                         if (opts.getOpt (requires [r]) == null)
    374                             opts.addError (formatMessage ("option \'" + opt.getName () + "\' requires option \'" + requires [r] +  "\'"));
    375                     }
    376                 }
    377 
    378                 // validation: "not with" constraints
    379 
    380                 final String [] excludes = optdef.getExcludesSet (); // not canonicalized
    381                 if (excludes != null)
    382                 {
    383                     for (int x = 0; x < excludes.length; ++ x)
    384                     {
    385                         final Opt xopt = opts.getOpt (excludes [x]);
    386                         if (xopt != null)
    387                             opts.addError (formatMessage ("option \'" + opt.getName () + "\' cannot be used with option \'" + xopt.getName () +  "\'"));
    388                     }
    389                 }
    390 
    391                 // side effect: determine if usage is requested
    392 
    393                 if (optdef.isUsage ())
    394                 {
    395                     opts.setUsageRequested (opt.getName ().equals (opt.getCanonicalName ()) ? SHORT_USAGE : DETAILED_USAGE);
    396                 }
    397             }
    398         }
    399 
    400         return opts;
    401     }
    402 
    403     private static String getOptCanonicalName (final String n, final OptDef optdef)
    404     {
    405         if (optdef.isPattern ())
    406         {
    407             final String canonicalPattern = optdef.getCanonicalName ();
    408             final String [] patterns = optdef.getNames ();
    409 
    410             for (int p = 0; p < patterns.length; ++ p)
    411             {
    412                 final String pattern = patterns [p];
    413 
    414                 if (n.startsWith (pattern))
    415                 {
    416                     return canonicalPattern.concat (n.substring (pattern.length ()));
    417                 }
    418             }
    419 
    420             // this should never happen:
    421             throw new IllegalStateException ("failed to detect pattern prefix for [" + n + "]");
    422         }
    423         else
    424         {
    425             return optdef.getCanonicalName ();
    426         }
    427     }
    428 
    429     /*
    430      * ['optdef' can be null if no current opt def context has been established]
    431      *
    432      * pre: av != null
    433      * input not validated
    434      */
    435     private static boolean isOpt (final String av, final int valueCount, final OptDef optdef)
    436     {
    437         if (optdef != null)
    438         {
    439             // if the current optdef calls for more values, consume the next token
    440             // as an op value greedily, without looking at its prefix:
    441 
    442             final int [] cardinality = optdef.getValueCardinality ();
    443 
    444             if (valueCount < cardinality [1]) return false;
    445         }
    446 
    447         // else check av's prefix:
    448 
    449         for (int p = 0; p < OPT_PREFIXES.length; ++ p)
    450         {
    451             if (av.startsWith (OPT_PREFIXES [p]))
    452                 return (av.length () > OPT_PREFIXES [p].length ());
    453         }
    454 
    455         return false;
    456     }
    457 
    458     /*
    459      * pre: av != null and isOpt(av)=true
    460      * input not validated
    461      */
    462     private static void getOptNameAndValue (final String av, final String [] nv)
    463     {
    464         nv [0] = null;
    465         nv [1] = null;
    466 
    467         for (int p = 0; p < OPT_PREFIXES.length; ++ p)
    468         {
    469             if ((av.startsWith (OPT_PREFIXES [p])) && (av.length () > OPT_PREFIXES [p].length ()))
    470             {
    471                 final String name = av.substring (OPT_PREFIXES [p].length ()); // with a possible value after a separator
    472 
    473                 char separator = 0;
    474                 int sindex = Integer.MAX_VALUE;
    475 
    476                 for (int s = 0; s < OPT_VALUE_SEPARATORS.length; ++ s)
    477                 {
    478                     final int index = name.indexOf (OPT_VALUE_SEPARATORS [s]);
    479                     if ((index > 0) && (index < sindex))
    480                     {
    481                         separator = OPT_VALUE_SEPARATORS [s];
    482                         sindex = index;
    483                     }
    484                 }
    485 
    486                 if (separator != 0)
    487                 {
    488                     nv [0] = name.substring (0, sindex);
    489                     nv [1] = name.substring (sindex + 1);
    490                 }
    491                 else
    492                 {
    493                     nv [0] = name;
    494                 }
    495 
    496                 return;
    497             }
    498         }
    499     }
    500 
    501     // protected: .............................................................
    502 
    503     // package: ...............................................................
    504 
    505 
    506     static final class Opt implements IOptsParser.IOpt
    507     {
    508         public String getName ()
    509         {
    510             return m_name;
    511         }
    512 
    513         public String getCanonicalName ()
    514         {
    515             return m_canonicalName;
    516         }
    517 
    518         public int getValueCount ()
    519         {
    520             if (m_values == null) return 0;
    521 
    522             return m_values.size ();
    523         }
    524 
    525         public String getFirstValue ()
    526         {
    527             if (m_values == null) return null;
    528 
    529             return (String) m_values.get (0);
    530         }
    531 
    532         public String [] getValues ()
    533         {
    534             if (m_values == null) return IConstants.EMPTY_STRING_ARRAY;
    535 
    536             final String [] result = new String [m_values.size ()];
    537             m_values.toArray (result);
    538 
    539             return result;
    540         }
    541 
    542         public String getPatternPrefix ()
    543         {
    544             return m_patternPrefix;
    545         }
    546 
    547         public String toString ()
    548         {
    549             final StringBuffer s = new StringBuffer (m_name);
    550             if (! m_canonicalName.equals (m_name)) s.append (" [" + m_canonicalName + "]");
    551 
    552             if (m_values != null)
    553             {
    554                 s.append (": ");
    555                 s.append (m_values);
    556             }
    557 
    558             return s.toString ();
    559         }
    560 
    561         Opt (final String name, final String canonicalName, final String patternPrefix)
    562         {
    563             m_name = name;
    564             m_canonicalName = canonicalName;
    565             m_patternPrefix = patternPrefix;
    566         }
    567 
    568         void addValue (final String value)
    569         {
    570             if (value == null) throw new IllegalArgumentException ("null input: value");
    571 
    572             if (m_values == null) m_values = new ArrayList ();
    573             m_values.add (value);
    574         }
    575 
    576 
    577         private final String m_name, m_canonicalName, m_patternPrefix;
    578         private ArrayList m_values;
    579 
    580     } // end of nested class
    581 
    582 
    583     static final class Opts implements IOptsParser.IOpts
    584     {
    585         public int usageRequestLevel ()
    586         {
    587             return m_usageRequestLevel;
    588         }
    589 
    590         public void error (final PrintWriter out, final int width)
    591         {
    592             // TODO: use width
    593             if (hasErrors ())
    594             {
    595                 for (Iterator i = m_errors.iterator (); i.hasNext (); )
    596                 {
    597                     out.println (i.next ());
    598                 }
    599             }
    600         }
    601 
    602         public String [] getFreeArgs ()
    603         {
    604             if (hasErrors ())
    605                 throw new IllegalStateException (errorsToString ());
    606 
    607             return m_freeArgs;
    608         }
    609 
    610         public IOpt [] getOpts ()
    611         {
    612             if (hasErrors ()) return null;
    613 
    614             if (m_opts.isEmpty ())
    615                 return EMPTY_OPT_ARRAY;
    616             else
    617             {
    618                 final IOpt [] result = new IOpt [m_opts.size ()];
    619                 m_opts.toArray (result);
    620 
    621                 return result;
    622             }
    623         }
    624 
    625         public IOpt [] getOpts (final String pattern)
    626         {
    627             if (hasErrors ()) return null;
    628 
    629             final List /* Opt */ patternOpts = (List) m_patternMap.get (pattern);
    630 
    631             if ((patternOpts == null) || patternOpts.isEmpty ())
    632                 return EMPTY_OPT_ARRAY;
    633             else
    634             {
    635                 final IOpt [] result = new IOpt [patternOpts.size ()];
    636                 patternOpts.toArray (result);
    637 
    638                 return result;
    639             }
    640         }
    641 
    642 
    643         public boolean hasArg (final String name)
    644         {
    645             if (hasErrors ())
    646                 throw new IllegalStateException (errorsToString ());
    647 
    648             return m_nameMap.containsKey (name);
    649         }
    650 
    651         Opts ()
    652         {
    653             m_opts = new ArrayList ();
    654             m_nameMap = new HashMap ();
    655             m_patternMap = new HashMap ();
    656         }
    657 
    658         void addOpt (final Opt opt, final OptDef optdef, final String occuranceName)
    659         {
    660             if (opt == null) throw new IllegalArgumentException ("null input: opt");
    661             if (optdef == null) throw new IllegalArgumentException ("null input: optdef");
    662             if (occuranceName == null) throw new IllegalArgumentException ("null input: occuranceName");
    663 
    664             // [name collisions detected elsewhere]
    665 
    666             m_opts.add (opt);
    667 
    668             final String [] names = optdef.getNames ();
    669             final boolean isPattern = (opt.getPatternPrefix () != null);
    670 
    671             if (isPattern)
    672             {
    673                 final String unprefixedName = occuranceName.substring (opt.getPatternPrefix ().length ());
    674 
    675                 for (int n = 0; n < names.length; ++ n)
    676                 {
    677                     m_nameMap.put (names [n].concat (unprefixedName), opt);
    678                 }
    679 
    680                 {
    681                     final String canonicalPattern = optdef.getCanonicalName ();
    682 
    683                     List patternList = (List) m_patternMap.get (canonicalPattern);
    684                     if (patternList == null)
    685                     {
    686                         patternList = new ArrayList ();
    687                         for (int n = 0; n < names.length; ++ n)
    688                         {
    689                             m_patternMap.put (names [n], patternList);
    690                         }
    691                     }
    692 
    693                     patternList.add (opt);
    694                 }
    695             }
    696             else
    697             {
    698                 for (int n = 0; n < names.length; ++ n)
    699                 {
    700                     m_nameMap.put (names [n], opt);
    701                 }
    702             }
    703         }
    704 
    705         Opt getOpt (final String occuranceName)
    706         {
    707             if (occuranceName == null) throw new IllegalArgumentException ("null input: occuranceName");
    708 
    709             return (Opt) m_nameMap.get (occuranceName);
    710         }
    711 
    712         void setFreeArgs (final String [] args, final int start)
    713         {
    714             if (args == null) throw new IllegalArgumentException ("null input: args");
    715             if ((start < 0) || (start > args.length)) throw new IllegalArgumentException ("invalid start index: " + start);
    716 
    717             m_freeArgs = new String [args.length - start];
    718             System.arraycopy (args, start, m_freeArgs, 0, m_freeArgs.length);
    719         }
    720 
    721         void setUsageRequested (final int level)
    722         {
    723             m_usageRequestLevel = level;
    724         }
    725 
    726         void addError (final String msg)
    727         {
    728             if (msg != null)
    729             {
    730                 if (m_errors == null) m_errors = new ArrayList ();
    731 
    732                 m_errors.add (msg);
    733             }
    734         }
    735 
    736         boolean hasErrors ()
    737         {
    738             return (m_errors != null) && ! m_errors.isEmpty ();
    739         }
    740 
    741         String errorsToString ()
    742         {
    743             if (! hasErrors ()) return "<no errors>";
    744 
    745             final CharArrayWriter caw = new CharArrayWriter ();
    746             final PrintWriter pw = new PrintWriter (caw);
    747 
    748             error (pw, DEFAULT_ERROR_WIDTH);
    749             pw.flush ();
    750 
    751             return caw.toString ();
    752         }
    753 
    754 
    755         private final List /* Opt */ m_opts;
    756         private final Map /* String(name/pattern-prefixed name)->Opt */ m_nameMap;
    757         private final Map /* String(pattern prefix)->List<Opt> */ m_patternMap;
    758         private String [] m_freeArgs;
    759         private List /* String */ m_errors;
    760         private int m_usageRequestLevel;
    761 
    762         private static final int DEFAULT_ERROR_WIDTH = 80;
    763         private static final IOpt [] EMPTY_OPT_ARRAY = new IOpt [0];
    764 
    765     } // end of nested class
    766 
    767 
    768     static final class OptDef // TODO: merge with Opt?
    769     {
    770         OptDef (final boolean usage)
    771         {
    772             m_usage = usage;
    773         }
    774 
    775         boolean isUsage ()
    776         {
    777             return m_usage;
    778         }
    779 
    780         String getCanonicalName ()
    781         {
    782             return m_names [0];
    783         }
    784 
    785         String [] getNames ()
    786         {
    787             return m_names;
    788         }
    789 
    790         boolean isRequired ()
    791         {
    792             return m_required;
    793         }
    794 
    795         String getValueMnemonic ()
    796         {
    797             return m_valueMnemonic;
    798         }
    799 
    800         boolean isMergeable ()
    801         {
    802             return m_mergeable;
    803         }
    804 
    805         boolean isDetailedOnly ()
    806         {
    807             return m_detailedOnly;
    808         }
    809 
    810         boolean isPattern ()
    811         {
    812             return m_pattern;
    813         }
    814 
    815         int [] getValueCardinality ()
    816         {
    817             return m_valueCardinality;
    818         }
    819 
    820         String [] getRequiresSet ()
    821         {
    822             return m_requiresSet;
    823         }
    824 
    825         String [] getExcludesSet ()
    826         {
    827             return m_excludesSet;
    828         }
    829 
    830         String getDescription ()
    831         {
    832             return m_description;
    833         }
    834 
    835         void setNames (final String [] names)
    836         {
    837             if (names == null) throw new IllegalArgumentException ("null input: names");
    838 
    839             m_names = names;
    840         }
    841 
    842         void setRequired (final boolean required)
    843         {
    844             m_required = required;
    845         }
    846 
    847         void setValueMnemonic (final String mnemonic)
    848         {
    849             if (mnemonic == null) throw new IllegalArgumentException ("null input: mnemonic");
    850 
    851             m_valueMnemonic = mnemonic;
    852         }
    853 
    854         void setMergeable (final boolean mergeable)
    855         {
    856             m_mergeable = mergeable;
    857         }
    858 
    859         void setDetailedOnly (final boolean detailedOnly)
    860         {
    861             m_detailedOnly = detailedOnly;
    862         }
    863 
    864         void setPattern (final boolean pattern)
    865         {
    866             m_pattern = pattern;
    867         }
    868 
    869         void setValueCardinality (final int [] cardinality)
    870         {
    871             if ((cardinality == null) || (cardinality.length != 2)) throw new IllegalArgumentException ("null or invalid input: cardinality");
    872 
    873             m_valueCardinality = cardinality;
    874         }
    875 
    876         void setRequiresSet (final String [] names)
    877         {
    878             if (names == null) throw new IllegalArgumentException ("null input: names");
    879 
    880             m_requiresSet = names.length > 0 ? names : null;
    881         }
    882 
    883         void setExcludesSet (final String [] names)
    884         {
    885             if (names == null) throw new IllegalArgumentException ("null input: names");
    886 
    887             m_excludesSet = names.length > 0 ? names : null;
    888         }
    889 
    890         void setDescription (final String description)
    891         {
    892             if (description == null) throw new IllegalArgumentException ("null input: description");
    893 
    894             m_description = description;
    895         }
    896 
    897 
    898         static final int [] C_ZERO = new int [] {0, 0};
    899         static final int [] C_ONE = new int [] {1, 1};
    900         static final int [] C_ZERO_OR_ONE = new int [] {0, 1};
    901         static final int [] C_ZERO_OR_MORE = new int [] {0, Integer.MAX_VALUE};
    902         static final int [] C_ONE_OR_MORE = new int [] {1, Integer.MAX_VALUE};
    903 
    904 
    905         private final boolean m_usage;
    906         private String [] m_names;
    907         private boolean m_required;
    908         private String m_valueMnemonic;
    909         private boolean m_mergeable;
    910         private boolean m_detailedOnly;
    911         private boolean m_pattern;
    912         private int [] m_valueCardinality;
    913         private String [] m_requiresSet, m_excludesSet;
    914         private String m_description;
    915 
    916     } // end of nested class
    917 
    918 
    919     static final class OptDefMetadata
    920     {
    921         OptDefMetadata ()
    922         {
    923             m_optdefs = new ArrayList ();
    924             m_optdefMap = new HashMap ();
    925             m_requiredOpts = new HashSet ();
    926             m_patternOptDefMap = new HashMap ();
    927         }
    928 
    929         OptDef getOptDef (final String name, final String [] prefixout)
    930         {
    931             if (name == null) throw new IllegalArgumentException ("null input: name");
    932 
    933             if (prefixout != null) prefixout [0] = null;
    934 
    935             // first, see if this is a regular option:
    936             OptDef result = (OptDef) m_optdefMap.get (name);
    937 
    938             // next, see if this is a prefixed option:
    939             if (result == null)
    940             {
    941                 for (Iterator ps = m_patternOptDefMap.entrySet ().iterator ();
    942                      ps.hasNext (); )
    943                 {
    944                     final Map.Entry entry = (Map.Entry) ps.next ();
    945                     final String pattern = (String) entry.getKey ();
    946 
    947                     if (name.startsWith (pattern))
    948                     {
    949                         if (prefixout != null) prefixout [0] = pattern;
    950                         result = (OptDef) entry.getValue ();
    951                         break;
    952                     }
    953                 }
    954             }
    955 
    956             return result;
    957         }
    958 
    959         Iterator /* OptDef */ getOptDefs ()
    960         {
    961             return m_optdefs.iterator ();
    962         }
    963 
    964         OptDef getPatternOptDefs (final String pattern) // returns null if no such pattern is defined
    965         {
    966             if (pattern == null) throw new IllegalArgumentException ("null input: pattern");
    967 
    968             return (OptDef) m_patternOptDefMap.get (pattern);
    969         }
    970 
    971         Set /* String(canonical name) */ getRequiredOpts ()
    972         {
    973             return m_requiredOpts;
    974         }
    975 
    976         OptDef getUsageOptDef ()
    977         {
    978             return m_usageOptDef;
    979         }
    980 
    981         void addOptDef (final OptDef optdef)
    982         {
    983             if (optdef == null) throw new IllegalArgumentException ("null input: optdef");
    984 
    985             final Map map = optdef.isPattern () ? m_patternOptDefMap : m_optdefMap;
    986             final String [] names = optdef.getNames ();
    987 
    988             for (int n = 0; n < names.length; ++ n)
    989             {
    990                 if (map.containsKey (names [n]))
    991                     throw new IllegalArgumentException ("duplicate option name [" + names [n] + "]");
    992 
    993                 map.put (names [n], optdef);
    994             }
    995 
    996             m_optdefs.add (optdef);
    997 
    998             if (optdef.isRequired ())
    999                 m_requiredOpts.add (optdef.getCanonicalName ());
   1000 
   1001             if (optdef.isUsage ())
   1002             {
   1003                 if (m_usageOptDef != null)
   1004                     throw new IllegalArgumentException ("usage optdef set already");
   1005 
   1006                 m_usageOptDef = optdef;
   1007             }
   1008         }
   1009 
   1010 
   1011         final List /* OptDef */ m_optdefs; // keeps the addition order
   1012         final Map /* String(name)->OptDef */ m_optdefMap;
   1013         final Set /* String(canonical name) */ m_requiredOpts;
   1014         final Map /* String(pattern name)->OptDef */ m_patternOptDefMap;
   1015         private OptDef m_usageOptDef;
   1016 
   1017     } // end of nested class
   1018 
   1019 
   1020     static final class MetadataParser
   1021     {
   1022         /*
   1023          * metadata := ( optdef )* <EOF>
   1024          *
   1025          * optdef := optnamelist ":" optmetadata ";"
   1026          * optnamelist := namelist
   1027          * optmetadata :=
   1028          *      ("optional" | "required" )
   1029          *      [ "," "mergeable" ]
   1030          *      [ "," "detailedonly" ]
   1031          *      [ "," "pattern" ]
   1032          *      "," "values" ":" cardinality
   1033          *      [ "," name ]
   1034          *      [ "," "requires" "{" namelist "}" ]
   1035          *      [ "," "notwith" "{" namelist "}" ]
   1036          *      "," text
   1037          * cardinality := "0" | "1" | "?"
   1038          * namelist := name ( "," name )*
   1039          * name := <single quoted string>
   1040          * text := <double quoted string>
   1041          */
   1042          OptDef [] parse (final Reader in)
   1043          {
   1044              if (in == null) throw new IllegalArgumentException ("null input: in");
   1045              m_in = in;
   1046 
   1047              nextChar ();
   1048              nextToken ();
   1049 
   1050              while (m_token != Token.EOF)
   1051              {
   1052                  if (m_opts == null) m_opts = new ArrayList ();
   1053                  m_opts.add (optdef ());
   1054              }
   1055 
   1056              final OptDef [] result;
   1057 
   1058              if ((m_opts == null) || (m_opts.size () == 0))
   1059                 result = EMPTY_OPTDEF_ARRAY;
   1060              else
   1061              {
   1062                  result = new OptDef [m_opts.size ()];
   1063                  m_opts.toArray (result);
   1064              }
   1065 
   1066              m_in = null;
   1067              m_opts = null;
   1068 
   1069              return result;
   1070          }
   1071 
   1072          OptDef optdef ()
   1073          {
   1074              final OptDef optdef = new OptDef (false);
   1075 
   1076              optdef.setNames (optnamelist ());
   1077              accept (Token.COLON_ID);
   1078              optmetadata (optdef);
   1079              accept (Token.SEMICOLON_ID);
   1080 
   1081              return optdef;
   1082          }
   1083 
   1084          String [] optnamelist ()
   1085          {
   1086              return namelist ();
   1087          }
   1088 
   1089          void optmetadata (final OptDef optdef)
   1090          {
   1091              switch (m_token.getID ())
   1092              {
   1093                  case Token.REQUIRED_ID:
   1094                  {
   1095                      accept ();
   1096                      optdef.setRequired (true);
   1097                  }
   1098                  break;
   1099 
   1100                  case Token.OPTIONAL_ID:
   1101                  {
   1102                      accept ();
   1103                      optdef.setRequired (false);
   1104                  }
   1105                  break;
   1106 
   1107                  default:
   1108                     throw new IllegalArgumentException ("parse error: invalid token " + m_token + ", expected " + Token.REQUIRED + " or " + Token.OPTIONAL);
   1109 
   1110              } // end of switch
   1111 
   1112              accept (Token.COMMA_ID);
   1113 
   1114              if (m_token.getID () == Token.MERGEABLE_ID)
   1115              {
   1116                  accept ();
   1117                  optdef.setMergeable (true);
   1118 
   1119                  accept (Token.COMMA_ID);
   1120              }
   1121 
   1122              if (m_token.getID () == Token.DETAILEDONLY_ID)
   1123              {
   1124                  accept ();
   1125                  optdef.setDetailedOnly (true);
   1126 
   1127                  accept (Token.COMMA_ID);
   1128              }
   1129 
   1130              if (m_token.getID () == Token.PATTERN_ID)
   1131              {
   1132                  accept ();
   1133                  optdef.setPattern (true);
   1134 
   1135                  accept (Token.COMMA_ID);
   1136              }
   1137 
   1138              accept (Token.VALUES_ID);
   1139              accept (Token.COLON_ID);
   1140              optdef.setValueCardinality (cardinality ());
   1141 
   1142              accept (Token.COMMA_ID);
   1143              if (m_token.getID () == Token.STRING_ID)
   1144              {
   1145                  optdef.setValueMnemonic (m_token.getValue ());
   1146                  accept ();
   1147 
   1148                  accept (Token.COMMA_ID);
   1149              }
   1150 
   1151              if (m_token.getID () == Token.REQUIRES_ID)
   1152              {
   1153                  accept ();
   1154 
   1155                  accept (Token.LBRACKET_ID);
   1156                  optdef.setRequiresSet (namelist ());
   1157                  accept (Token.RBRACKET_ID);
   1158 
   1159                  accept (Token.COMMA_ID);
   1160              }
   1161 
   1162              if (m_token.getID () == Token.EXCLUDES_ID)
   1163              {
   1164                  accept ();
   1165 
   1166                  accept (Token.LBRACKET_ID);
   1167                  optdef.setExcludesSet (namelist ());
   1168                  accept (Token.RBRACKET_ID);
   1169 
   1170                  accept (Token.COMMA_ID);
   1171              }
   1172 
   1173              optdef.setDescription (accept (Token.TEXT_ID).getValue ());
   1174          }
   1175 
   1176          int [] cardinality ()
   1177          {
   1178              final Token result = accept (Token.CARD_ID);
   1179 
   1180              if ("0".equals (result.getValue ()))
   1181                 return OptDef.C_ZERO;
   1182              else if ("1".equals (result.getValue ()))
   1183                 return OptDef.C_ONE;
   1184              else // ?
   1185                 return OptDef.C_ZERO_OR_ONE;
   1186          }
   1187 
   1188          String [] namelist ()
   1189          {
   1190              final List _result = new ArrayList ();
   1191 
   1192              _result.add (accept (Token.STRING_ID).getValue ());
   1193              while (m_token.getID () == Token.COMMA_ID)
   1194              {
   1195                  accept ();
   1196                  _result.add (accept (Token.STRING_ID).getValue ());
   1197              }
   1198 
   1199              final String [] result = new String [_result.size ()];
   1200              _result.toArray (result);
   1201 
   1202              return result;
   1203          }
   1204 
   1205 
   1206          Token accept ()
   1207          {
   1208              final Token current = m_token;
   1209              nextToken ();
   1210 
   1211              return current;
   1212          }
   1213 
   1214          Token accept (final int tokenID)
   1215          {
   1216              final Token current = m_token;
   1217 
   1218              if (m_token.getID () == tokenID)
   1219                 nextToken ();
   1220              else
   1221                 throw new IllegalArgumentException ("parse error: invalid token [" + m_token + "], expected type [" + tokenID + "]");
   1222 
   1223              return current;
   1224          }
   1225 
   1226          // "scanner":
   1227 
   1228          void nextToken ()
   1229          {
   1230              consumeWS ();
   1231 
   1232              switch (m_currentChar)
   1233              {
   1234                  case -1: m_token = Token.EOF; break;
   1235 
   1236                  case ':':
   1237                  {
   1238                      nextChar ();
   1239                      m_token = Token.COLON;
   1240                  }
   1241                  break;
   1242 
   1243                  case ';':
   1244                  {
   1245                      nextChar ();
   1246                      m_token = Token.SEMICOLON;
   1247                  }
   1248                  break;
   1249 
   1250                  case ',':
   1251                  {
   1252                      nextChar ();
   1253                      m_token = Token.COMMA;
   1254                  }
   1255                  break;
   1256 
   1257                  case '{':
   1258                  {
   1259                      nextChar ();
   1260                      m_token = Token.LBRACKET;
   1261                  }
   1262                  break;
   1263 
   1264                  case '}':
   1265                  {
   1266                      nextChar ();
   1267                      m_token = Token.RBRACKET;
   1268                  }
   1269                  break;
   1270 
   1271                  case '0':
   1272                  {
   1273                      nextChar ();
   1274                      m_token = new Token (Token.CARD_ID, "0");
   1275                  }
   1276                  break;
   1277 
   1278                  case '1':
   1279                  {
   1280                      nextChar ();
   1281                      m_token = new Token (Token.CARD_ID, "1");
   1282                  }
   1283                  break;
   1284 
   1285                  case '?':
   1286                  {
   1287                      nextChar ();
   1288                      m_token = new Token (Token.CARD_ID, "?");
   1289                  }
   1290                  break;
   1291 
   1292                  case '\'':
   1293                  {
   1294                      final StringBuffer value = new StringBuffer ();
   1295 
   1296                      nextChar ();
   1297                      while (m_currentChar != '\'')
   1298                      {
   1299                          value.append ((char) m_currentChar);
   1300                          nextChar ();
   1301                      }
   1302                      nextChar ();
   1303 
   1304                      m_token = new Token (Token.STRING_ID, value.toString ());
   1305                  }
   1306                  break;
   1307 
   1308                  case '\"':
   1309                  {
   1310                      final StringBuffer value = new StringBuffer ();
   1311 
   1312                      nextChar ();
   1313                      while (m_currentChar != '\"')
   1314                      {
   1315                          value.append ((char) m_currentChar);
   1316                          nextChar ();
   1317                      }
   1318                      nextChar ();
   1319 
   1320                      m_token = new Token (Token.TEXT_ID, value.toString ());
   1321                  }
   1322                  break;
   1323 
   1324                  default:
   1325                  {
   1326                      final StringBuffer value = new StringBuffer ();
   1327 
   1328                      while (Character.isLetter ((char) m_currentChar))
   1329                      {
   1330                          value.append ((char) m_currentChar);
   1331                          nextChar ();
   1332                      }
   1333 
   1334                      final Token token = (Token) KEYWORDS.get (value.toString ());
   1335                      if (token == null)
   1336                         throw new IllegalArgumentException ("parse error: unrecognized keyword [" + value  + "]");
   1337 
   1338                      m_token = token;
   1339                  }
   1340 
   1341              } // end of switch
   1342          }
   1343 
   1344 
   1345          private void consumeWS ()
   1346          {
   1347              if (m_currentChar == -1)
   1348                 return;
   1349              else
   1350              {
   1351                  while (Character.isWhitespace ((char) m_currentChar))
   1352                  {
   1353                     nextChar ();
   1354                  }
   1355              }
   1356 
   1357              // TODO: #-comments
   1358          }
   1359 
   1360          private void nextChar ()
   1361          {
   1362              try
   1363              {
   1364                 m_currentChar = m_in.read ();
   1365              }
   1366              catch (IOException ioe)
   1367              {
   1368                  throw new RuntimeException ("I/O error while parsing: " + ioe);
   1369              }
   1370          }
   1371 
   1372 
   1373          private Reader m_in;
   1374          private List m_opts;
   1375 
   1376          private Token m_token;
   1377          private int m_currentChar;
   1378 
   1379          private static final Map KEYWORDS;
   1380 
   1381          private static final OptDef [] EMPTY_OPTDEF_ARRAY = new OptDef [0];
   1382 
   1383          static
   1384          {
   1385              KEYWORDS = new HashMap (17);
   1386 
   1387              KEYWORDS.put (Token.OPTIONAL.getValue (), Token.OPTIONAL);
   1388              KEYWORDS.put (Token.REQUIRED.getValue (), Token.REQUIRED);
   1389              KEYWORDS.put (Token.VALUES.getValue (), Token.VALUES);
   1390              KEYWORDS.put (Token.REQUIRES.getValue (), Token.REQUIRES);
   1391              KEYWORDS.put (Token.EXCLUDES.getValue (), Token.EXCLUDES);
   1392              KEYWORDS.put (Token.MERGEABLE.getValue (), Token.MERGEABLE);
   1393              KEYWORDS.put (Token.DETAILEDONLY.getValue (), Token.DETAILEDONLY);
   1394              KEYWORDS.put (Token.PATTERN.getValue (), Token.PATTERN);
   1395          }
   1396 
   1397     } // end of nested class
   1398 
   1399 
   1400     OptsParser (final String metadataResourceName, final ClassLoader loader, final String [] usageOpts)
   1401     {
   1402         this (metadataResourceName, loader, null, usageOpts);
   1403     }
   1404 
   1405     OptsParser (final String metadataResourceName, final ClassLoader loader, final String msgPrefix, final String [] usageOpts)
   1406     {
   1407         if (metadataResourceName == null) throw new IllegalArgumentException ("null input: metadataResourceName");
   1408 
   1409         m_msgPrefix = msgPrefix;
   1410 
   1411         InputStream in = null;
   1412         try
   1413         {
   1414             in = ResourceLoader.getResourceAsStream (metadataResourceName, loader);
   1415             if (in == null)
   1416                 throw new IllegalArgumentException ("resource [" + metadataResourceName + "] could not be loaded via [" + loader + "]");
   1417 
   1418             // TODO: encoding
   1419             final Reader rin = new InputStreamReader (in);
   1420 
   1421             m_metadata = parseOptDefMetadata (rin, usageOpts);
   1422         }
   1423         finally
   1424         {
   1425             if (in != null) try { in.close (); } catch (IOException ignore) {}
   1426         }
   1427     }
   1428 
   1429     // private: ...............................................................
   1430 
   1431 
   1432     private static final class Token
   1433     {
   1434         Token (final int ID, final String value)
   1435         {
   1436             if (value == null) throw new IllegalArgumentException ("null input: value");
   1437 
   1438             m_ID = ID;
   1439             m_value = value;
   1440         }
   1441 
   1442         int getID ()
   1443         {
   1444             return m_ID;
   1445         }
   1446 
   1447         String getValue ()
   1448         {
   1449             return m_value;
   1450         }
   1451 
   1452         public String toString ()
   1453         {
   1454             return m_ID + ": [" + m_value + "]";
   1455         }
   1456 
   1457 
   1458         static final int EOF_ID = 0;
   1459         static final int STRING_ID = 1;
   1460         static final int COLON_ID = 2;
   1461         static final int SEMICOLON_ID = 3;
   1462         static final int COMMA_ID = 4;
   1463         static final int LBRACKET_ID = 5;
   1464         static final int RBRACKET_ID = 6;
   1465         static final int OPTIONAL_ID = 7;
   1466         static final int REQUIRED_ID = 8;
   1467         static final int CARD_ID = 9;
   1468         static final int VALUES_ID = 10;
   1469         static final int TEXT_ID = 11;
   1470         static final int REQUIRES_ID = 12;
   1471         static final int EXCLUDES_ID = 13;
   1472         static final int MERGEABLE_ID = 14;
   1473         static final int DETAILEDONLY_ID = 15;
   1474         static final int PATTERN_ID = 16;
   1475 
   1476         static final Token EOF = new Token (EOF_ID, "<EOF>");
   1477         static final Token COLON = new Token (COLON_ID, ":");
   1478         static final Token SEMICOLON = new Token (SEMICOLON_ID, ";");
   1479         static final Token COMMA = new Token (COMMA_ID, ",");
   1480         static final Token LBRACKET = new Token (LBRACKET_ID, "{");
   1481         static final Token RBRACKET = new Token (RBRACKET_ID, "}");
   1482         static final Token OPTIONAL = new Token (OPTIONAL_ID, "optional");
   1483         static final Token REQUIRED = new Token (REQUIRED_ID, "required");
   1484         static final Token VALUES = new Token (VALUES_ID, "values");
   1485         static final Token REQUIRES = new Token (REQUIRES_ID, "requires");
   1486         static final Token EXCLUDES = new Token (EXCLUDES_ID, "excludes");
   1487         static final Token MERGEABLE = new Token (MERGEABLE_ID, "mergeable");
   1488         static final Token DETAILEDONLY = new Token (DETAILEDONLY_ID, "detailedonly");
   1489         static final Token PATTERN = new Token (PATTERN_ID, "pattern");
   1490 
   1491         private final int m_ID;
   1492         private final String m_value;
   1493 
   1494     } // end of nested class
   1495 
   1496 
   1497     private static OptDefMetadata parseOptDefMetadata (final Reader in, final String [] usageOpts)
   1498     {
   1499         final MetadataParser parser = new MetadataParser ();
   1500         final OptDef [] optdefs = parser.parse (in);
   1501 
   1502         // validate:
   1503 
   1504 //        for (int o = 0; o < optdefs.length; ++ o)
   1505 //        {
   1506 //            final OptDef optdef = optdefs [o];
   1507 //            final int [] cardinality = optdef.getValueCardinality ();
   1508 //
   1509 //            if (optdef.isMergeable ())
   1510 //            {
   1511 //                if ((cardinality [1] != 0) && (cardinality [1] != Integer.MAX_VALUE))
   1512 //                    throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] is mergeable and can only specify {0, +inf} for max value cardinality: " + cardinality [1]);
   1513 //            }
   1514 //        }
   1515 
   1516         final OptDefMetadata result = new OptDefMetadata ();
   1517         for (int o = 0; o < optdefs.length; ++ o)
   1518         {
   1519             result.addOptDef (optdefs [o]);
   1520         }
   1521 
   1522         // add usage opts:
   1523         if (usageOpts != null)
   1524         {
   1525             final OptDef usage = new OptDef (true);
   1526 
   1527             usage.setNames (usageOpts);
   1528             usage.setDescription ("display usage information");
   1529             usage.setValueCardinality (OptDef.C_ZERO);
   1530             usage.setRequired (false);
   1531             usage.setDetailedOnly (false);
   1532             usage.setMergeable (false);
   1533 
   1534             result.addOptDef (usage);
   1535         }
   1536 
   1537         // TODO: fix this to be pattern-savvy
   1538 
   1539         for (int o = 0; o < optdefs.length; ++ o)
   1540         {
   1541             final OptDef optdef = optdefs [o];
   1542 
   1543             final String [] requires = optdef.getRequiresSet ();
   1544             if (requires != null)
   1545             {
   1546                 for (int r = 0; r < requires.length; ++ r)
   1547                 {
   1548                     final OptDef ropt = result.getOptDef (requires [r], null);
   1549                     if (ropt == null)
   1550                         throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + requires [r] + "] in its \'requires\' set");
   1551 
   1552                     if (ropt == optdef)
   1553                         throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'requires\' set");
   1554                 }
   1555             }
   1556 
   1557             final String [] excludes = optdef.getExcludesSet ();
   1558             if (excludes != null)
   1559             {
   1560                 for (int x = 0; x < excludes.length; ++ x)
   1561                 {
   1562                     final OptDef xopt = result.getOptDef (excludes [x], null);
   1563                     if (xopt == null)
   1564                         throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + excludes [x] + "] in its \'excludes\' set");
   1565 
   1566                     if (xopt.isRequired ())
   1567                         throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies a required option [" + excludes [x] + "] in its \'excludes\' set");
   1568 
   1569                     if (xopt == optdef)
   1570                         throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'excludes\' set");
   1571                 }
   1572             }
   1573         }
   1574 
   1575         return result;
   1576     }
   1577 
   1578     private String formatMessage (final String msg)
   1579     {
   1580         if (m_msgPrefix == null) return msg;
   1581         else
   1582         {
   1583             return m_msgPrefix.concat (msg);
   1584         }
   1585     }
   1586 
   1587 
   1588     private final String m_msgPrefix;
   1589     private final OptDefMetadata m_metadata;
   1590 
   1591     private static final int CANONICAL_OPT_PREFIX = 1; // indexes into OPT_PREFIXES
   1592     private static final String [] OPT_PREFIXES = new String [] {"--", "-"}; // HACK: these must appear in decreasing length order
   1593     private static final char [] OPT_VALUE_SEPARATORS = new char [] {':', '='};
   1594 
   1595     private static final int STATE_OPT = 0, STATE_OPT_VALUE = 1, STATE_FREE_ARGS = 2, STATE_ERROR = 3;
   1596 
   1597 } // end of class
   1598 // ----------------------------------------------------------------------------