• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
usage(final PrintWriter out, final int level, final int width)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 
parse(final String [] args)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 
getOptCanonicalName(final String n, final OptDef optdef)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      */
isOpt(final String av, final int valueCount, final OptDef optdef)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      */
getOptNameAndValue(final String av, final String [] nv)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     {
getName()508         public String getName ()
509         {
510             return m_name;
511         }
512 
getCanonicalName()513         public String getCanonicalName ()
514         {
515             return m_canonicalName;
516         }
517 
getValueCount()518         public int getValueCount ()
519         {
520             if (m_values == null) return 0;
521 
522             return m_values.size ();
523         }
524 
getFirstValue()525         public String getFirstValue ()
526         {
527             if (m_values == null) return null;
528 
529             return (String) m_values.get (0);
530         }
531 
getValues()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 
getPatternPrefix()542         public String getPatternPrefix ()
543         {
544             return m_patternPrefix;
545         }
546 
toString()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 
Opt(final String name, final String canonicalName, final String patternPrefix)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 
addValue(final String value)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     {
usageRequestLevel()585         public int usageRequestLevel ()
586         {
587             return m_usageRequestLevel;
588         }
589 
error(final PrintWriter out, final int width)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 
getFreeArgs()602         public String [] getFreeArgs ()
603         {
604             if (hasErrors ())
605                 throw new IllegalStateException (errorsToString ());
606 
607             return m_freeArgs;
608         }
609 
getOpts()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 
getOpts(final String pattern)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 
hasArg(final String name)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 
Opts()651         Opts ()
652         {
653             m_opts = new ArrayList ();
654             m_nameMap = new HashMap ();
655             m_patternMap = new HashMap ();
656         }
657 
addOpt(final Opt opt, final OptDef optdef, final String occuranceName)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 
getOpt(final String occuranceName)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 
setFreeArgs(final String [] args, final int start)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 
setUsageRequested(final int level)721         void setUsageRequested (final int level)
722         {
723             m_usageRequestLevel = level;
724         }
725 
addError(final String msg)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 
hasErrors()736         boolean hasErrors ()
737         {
738             return (m_errors != null) && ! m_errors.isEmpty ();
739         }
740 
errorsToString()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     {
OptDef(final boolean usage)770         OptDef (final boolean usage)
771         {
772             m_usage = usage;
773         }
774 
isUsage()775         boolean isUsage ()
776         {
777             return m_usage;
778         }
779 
getCanonicalName()780         String getCanonicalName ()
781         {
782             return m_names [0];
783         }
784 
getNames()785         String [] getNames ()
786         {
787             return m_names;
788         }
789 
isRequired()790         boolean isRequired ()
791         {
792             return m_required;
793         }
794 
getValueMnemonic()795         String getValueMnemonic ()
796         {
797             return m_valueMnemonic;
798         }
799 
isMergeable()800         boolean isMergeable ()
801         {
802             return m_mergeable;
803         }
804 
isDetailedOnly()805         boolean isDetailedOnly ()
806         {
807             return m_detailedOnly;
808         }
809 
isPattern()810         boolean isPattern ()
811         {
812             return m_pattern;
813         }
814 
getValueCardinality()815         int [] getValueCardinality ()
816         {
817             return m_valueCardinality;
818         }
819 
getRequiresSet()820         String [] getRequiresSet ()
821         {
822             return m_requiresSet;
823         }
824 
getExcludesSet()825         String [] getExcludesSet ()
826         {
827             return m_excludesSet;
828         }
829 
getDescription()830         String getDescription ()
831         {
832             return m_description;
833         }
834 
setNames(final String [] names)835         void setNames (final String [] names)
836         {
837             if (names == null) throw new IllegalArgumentException ("null input: names");
838 
839             m_names = names;
840         }
841 
setRequired(final boolean required)842         void setRequired (final boolean required)
843         {
844             m_required = required;
845         }
846 
setValueMnemonic(final String mnemonic)847         void setValueMnemonic (final String mnemonic)
848         {
849             if (mnemonic == null) throw new IllegalArgumentException ("null input: mnemonic");
850 
851             m_valueMnemonic = mnemonic;
852         }
853 
setMergeable(final boolean mergeable)854         void setMergeable (final boolean mergeable)
855         {
856             m_mergeable = mergeable;
857         }
858 
setDetailedOnly(final boolean detailedOnly)859         void setDetailedOnly (final boolean detailedOnly)
860         {
861             m_detailedOnly = detailedOnly;
862         }
863 
setPattern(final boolean pattern)864         void setPattern (final boolean pattern)
865         {
866             m_pattern = pattern;
867         }
868 
setValueCardinality(final int [] cardinality)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 
setRequiresSet(final String [] names)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 
setExcludesSet(final String [] names)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 
setDescription(final String description)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     {
OptDefMetadata()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 
getOptDef(final String name, final String [] prefixout)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 
getOptDefs()959         Iterator /* OptDef */ getOptDefs ()
960         {
961             return m_optdefs.iterator ();
962         }
963 
getPatternOptDefs(final String pattern)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 
getRequiredOpts()971         Set /* String(canonical name) */ getRequiredOpts ()
972         {
973             return m_requiredOpts;
974         }
975 
getUsageOptDef()976         OptDef getUsageOptDef ()
977         {
978             return m_usageOptDef;
979         }
980 
addOptDef(final OptDef optdef)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          */
parse(final Reader in)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 
optdef()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 
optnamelist()1084          String [] optnamelist ()
1085          {
1086              return namelist ();
1087          }
1088 
optmetadata(final OptDef optdef)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 
cardinality()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 
namelist()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 
accept()1206          Token accept ()
1207          {
1208              final Token current = m_token;
1209              nextToken ();
1210 
1211              return current;
1212          }
1213 
accept(final int tokenID)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 
nextToken()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 
consumeWS()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 
nextChar()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 
Token.OPTIONAL.getValue()1387              KEYWORDS.put (Token.OPTIONAL.getValue (), Token.OPTIONAL);
Token.REQUIRED.getValue()1388              KEYWORDS.put (Token.REQUIRED.getValue (), Token.REQUIRED);
Token.VALUES.getValue()1389              KEYWORDS.put (Token.VALUES.getValue (), Token.VALUES);
Token.REQUIRES.getValue()1390              KEYWORDS.put (Token.REQUIRES.getValue (), Token.REQUIRES);
Token.EXCLUDES.getValue()1391              KEYWORDS.put (Token.EXCLUDES.getValue (), Token.EXCLUDES);
Token.MERGEABLE.getValue()1392              KEYWORDS.put (Token.MERGEABLE.getValue (), Token.MERGEABLE);
Token.DETAILEDONLY.getValue()1393              KEYWORDS.put (Token.DETAILEDONLY.getValue (), Token.DETAILEDONLY);
Token.PATTERN.getValue()1394              KEYWORDS.put (Token.PATTERN.getValue (), Token.PATTERN);
1395          }
1396 
1397     } // end of nested class
1398 
1399 
OptsParser(final String metadataResourceName, final ClassLoader loader, final String [] usageOpts)1400     OptsParser (final String metadataResourceName, final ClassLoader loader, final String [] usageOpts)
1401     {
1402         this (metadataResourceName, loader, null, usageOpts);
1403     }
1404 
OptsParser(final String metadataResourceName, final ClassLoader loader, final String msgPrefix, final String [] usageOpts)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     {
Token(final int ID, final String value)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 
getID()1442         int getID ()
1443         {
1444             return m_ID;
1445         }
1446 
getValue()1447         String getValue ()
1448         {
1449             return m_value;
1450         }
1451 
toString()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 
parseOptDefMetadata(final Reader in, final String [] usageOpts)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 
formatMessage(final String msg)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 // ----------------------------------------------------------------------------