FileDocCategorySizeDatePackage
OptsParser.javaAPI DocAndroid 1.5 API54647Wed May 06 22:41:16 BST 2009com.vladium.util.args

OptsParser

public final class OptsParser extends Object implements IOptsParser
author
Vlad Roubtsov, (C) 2002

Fields Summary
private final String
m_msgPrefix
private final OptDefMetadata
m_metadata
private static final int
CANONICAL_OPT_PREFIX
private static final String[]
OPT_PREFIXES
private static final char[]
OPT_VALUE_SEPARATORS
private static final int
STATE_OPT
private static final int
STATE_OPT_VALUE
private static final int
STATE_FREE_ARGS
private static final int
STATE_ERROR
Constructors Summary
OptsParser(String metadataResourceName, ClassLoader loader, String[] usageOpts)

         
         
         
             KEYWORDS = new HashMap (17);
             
             KEYWORDS.put (Token.OPTIONAL.getValue (), Token.OPTIONAL);
             KEYWORDS.put (Token.REQUIRED.getValue (), Token.REQUIRED);
             KEYWORDS.put (Token.VALUES.getValue (), Token.VALUES);
             KEYWORDS.put (Token.REQUIRES.getValue (), Token.REQUIRES);
             KEYWORDS.put (Token.EXCLUDES.getValue (), Token.EXCLUDES);
             KEYWORDS.put (Token.MERGEABLE.getValue (), Token.MERGEABLE);
             KEYWORDS.put (Token.DETAILEDONLY.getValue (), Token.DETAILEDONLY);
             KEYWORDS.put (Token.PATTERN.getValue (), Token.PATTERN);
         
        this (metadataResourceName, loader, null, usageOpts);
    
OptsParser(String metadataResourceName, ClassLoader loader, String msgPrefix, String[] usageOpts)

        if (metadataResourceName == null) throw new IllegalArgumentException ("null input: metadataResourceName");
        
        m_msgPrefix = msgPrefix;
        
        InputStream in = null;
        try
        {
            in = ResourceLoader.getResourceAsStream (metadataResourceName, loader);
            if (in == null)
                throw new IllegalArgumentException ("resource [" + metadataResourceName + "] could not be loaded via [" + loader + "]");
            
            // TODO: encoding
            final Reader rin = new InputStreamReader (in);
            
            m_metadata = parseOptDefMetadata (rin, usageOpts);   
        }
        finally
        {
            if (in != null) try { in.close (); } catch (IOException ignore) {} 
        }
    
Methods Summary
private java.lang.StringformatMessage(java.lang.String msg)

        if (m_msgPrefix == null) return msg;
        else
        {
            return m_msgPrefix.concat (msg);
        }
    
private static java.lang.StringgetOptCanonicalName(java.lang.String n, com.vladium.util.args.OptsParser$OptDef optdef)

        if (optdef.isPattern ())
        {
            final String canonicalPattern = optdef.getCanonicalName ();
            final String [] patterns = optdef.getNames ();
            
            for (int p = 0; p < patterns.length; ++ p)
            {
                final String pattern = patterns [p];
                
                if (n.startsWith (pattern))
                {
                    return canonicalPattern.concat (n.substring (pattern.length ()));
                }
            }
            
            // this should never happen:
            throw new IllegalStateException ("failed to detect pattern prefix for [" + n + "]");
        }
        else
        {
            return optdef.getCanonicalName ();
        }
    
private static voidgetOptNameAndValue(java.lang.String av, java.lang.String[] nv)

        nv [0] = null;
        nv [1] = null;
        
        for (int p = 0; p < OPT_PREFIXES.length; ++ p)
        {
            if ((av.startsWith (OPT_PREFIXES [p])) && (av.length () > OPT_PREFIXES [p].length ()))
            {
                final String name = av.substring (OPT_PREFIXES [p].length ()); // with a possible value after a separator
                
                char separator = 0;
                int sindex = Integer.MAX_VALUE;
                
                for (int s = 0; s < OPT_VALUE_SEPARATORS.length; ++ s)
                {
                    final int index = name.indexOf (OPT_VALUE_SEPARATORS [s]);
                    if ((index > 0) && (index < sindex))
                    {
                        separator = OPT_VALUE_SEPARATORS [s];
                        sindex = index;
                    }
                }
                
                if (separator != 0)
                {
                    nv [0] = name.substring (0, sindex);
                    nv [1] = name.substring (sindex + 1);
                }
                else
                {
                    nv [0] = name;
                }
                
                return;
            }
        }
    
private static booleanisOpt(java.lang.String av, int valueCount, com.vladium.util.args.OptsParser$OptDef optdef)

        if (optdef != null)
        {
            // if the current optdef calls for more values, consume the next token
            // as an op value greedily, without looking at its prefix:
        
            final int [] cardinality = optdef.getValueCardinality ();
        
            if (valueCount < cardinality [1]) return false;
        }
        
        // else check av's prefix:
        
        for (int p = 0; p < OPT_PREFIXES.length; ++ p)
        {
            if (av.startsWith (OPT_PREFIXES [p]))
                return (av.length () > OPT_PREFIXES [p].length ());
        }
        
        return false;
    
public synchronized IOptsparse(java.lang.String[] args)

        if (args == null) throw new IllegalArgumentException ("null input: args");
        
        final Opts opts = new Opts ();
        
        {
            final String [] nv = new String [2]; // out buffer for getOptNameAndValue()
            final String [] pp = new String [1]; // out buffer for getOptDef()
            
            // running state/current vars:
            int state = STATE_OPT;
            OptDef optdef = null;
            Opt opt = null;
            String value = null;
            int valueCount = 0;
            
            int a;
      scan: for (a = 0; a < args.length; )
            {
                final String av = args [a];
                if (av == null) throw new IllegalArgumentException ("null input: args[" + a + "]");
                
                //System.out.println ("[state: " + state + "] av = " + av);
                
                switch (state)
                {
                    case STATE_OPT:
                    {
                        if (isOpt (av, valueCount, optdef))
                        {
                            // 'av' looks like an option: get its name and see if it
                            // is in the metadata
                            
                            valueCount = 0;
                           
                            getOptNameAndValue (av, nv); // this can leave nv[1] as null
                           
                            // [assertion: nv [0] != null]
                           
                            final String optName = nv [0]; // is not necessarily canonical
                            optdef = m_metadata.getOptDef (optName, pp); // pp [0] is always set by this
                           
                            if (optdef == null)
                            {
                                // unknown option:
                               
                                // TODO: coded messages?
                                opts.addError (formatMessage ("unknown option \'" + optName + "\'"));
                               
                                state = STATE_ERROR;
                            }
                            else
                            {
                                // merge if necessary:
                                
                                final String canonicalName = getOptCanonicalName (optName, optdef);
                                final String patternPrefix = pp [0];
    
                                opt = opts.getOpt (canonicalName);
                                
                                if (optdef.isMergeable ())
                                {
                                    if (opt == null)
                                    {
                                        opt = new Opt (optName, canonicalName, patternPrefix);
                                        opts.addOpt (opt, optdef, optName);
                                    }
                                }
                                else
                                {
                                    if (opt == null)
                                    {
                                        opt = new Opt (optName, canonicalName, patternPrefix);
                                        opts.addOpt (opt, optdef, optName);
                                    }
                                    else
                                    {
                                        opts.addError (formatMessage ("option \'" + optName + "\' cannot be specified more than once"));
                               
                                        state = STATE_ERROR;
                                    }
                                }

                                value = nv [1];
                                
                                if (value == null) ++ a;
                                state = STATE_OPT_VALUE;
                            }
                        }
                        else
                        {
                            // not in STATE_OPT_VALUE and 'av' does not look
                            // like an option: the rest of args are free
                           
                            state = STATE_FREE_ARGS;
                        }
                    }
                    break;
                    
                    
                    case STATE_OPT_VALUE:
                    {
                        // [assertion: opt != null and optdef != null]
                        
                        if (value != null)
                        {
                            // value specified explicitly using the <name>separator<value> syntax:
                            // [don't shift a]
                            
                            valueCount = 1;
                             
                            final int [] cardinality = optdef.getValueCardinality ();
                            
                            if (cardinality [1] < 1)
                            {
                                opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept values: \'" + value + "\'"));
                               
                                state = STATE_ERROR;
                            }
                            else
                            {
                                ++ a;
                                opt.addValue (value);
                            }
                        }
                        else
                        {
                            value = args [a];
                            
                            final int [] cardinality = optdef.getValueCardinality ();
                        
                            if (isOpt (value, valueCount, optdef))
                            {
                                if (valueCount < cardinality [0])
                                {
                                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
                                   
                                    state = STATE_ERROR;
                                }
                                else
                                    state = STATE_OPT;
                            }
                            else
                            {
                                if (valueCount < cardinality [1])
                                {
                                    ++ valueCount;
                                    ++ a;
                                    opt.addValue (value);
                                }
                                else
                                {
                                    // this check is redundant:
//                                    if (valueCount < cardinality [0])
//                                    {
//                                        opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
//                                       
//                                        state = STATE_ERROR;
//                                    }
//                                    else
                                        state = STATE_FREE_ARGS;
                                } 
                            }
                        }
                        
                        value = null;
                    }
                    break;
                    
                    
                    case STATE_FREE_ARGS:
                    {
                        if (isOpt (args [a], valueCount, optdef))
                        {
                            state = STATE_OPT;
                        }
                        else
                        {
                            opts.setFreeArgs (args, a);
                            break scan;
                        }
                    }
                    break;
                    
                    
                    case STATE_ERROR:
                    {
                        break scan; // TODO: could use the current value of 'a' for a better error message
                    }
                    
                } // end of switch
            }
            
            if (a == args.length)
            {
                if (opt != null) // validate the last option's min cardinality
                {
                    final int [] cardinality = optdef.getValueCardinality ();
                    
                    if (valueCount < cardinality [0])
                    {
                        opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)"));
                    }
                }
                else
                {
                    opts.setFreeArgs (args, a);
                }
            }
            
        } // end of 'args' parsing
        
        
        final IOpt [] specified = opts.getOpts ();
        if (specified != null)
        {
            // validation: all required parameters must be specified
            
            final Set /* String(canonical name) */ required = new HashSet ();
            required.addAll (m_metadata.getRequiredOpts ());
            
            for (int s = 0; s < specified.length; ++ s)
            {
                required.remove (specified [s].getCanonicalName ());
            }
            
            if (! required.isEmpty ())
            {
                for (Iterator i = required.iterator (); i.hasNext (); )
                {
                    opts.addError (formatMessage ("missing required option \'" + (String) i.next () + "\'"));
                }
            }
            
            for (int s = 0; s < specified.length; ++ s)
            {
                final IOpt opt = specified [s];
                final OptDef optdef = m_metadata.getOptDef (opt.getCanonicalName (), null); 
                
//                // validation: value cardinality constraints
//                
//                final int [] cardinality = optdef.getValueCardinality ();
//                if (opt.getValueCount () < cardinality [0])
//                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' must have at least " + cardinality [0] +  " value(s)"));
//                else if (opt.getValueCount () > cardinality [1])
//                    opts.addError (formatMessage ("option \'" + opt.getName () + "\' must not have more than " + cardinality [1] +  " value(s)"));
                
                // validation: "requires" constraints
                
                final String [] requires = optdef.getRequiresSet (); // not canonicalized
                if (requires != null)
                {
                    for (int r = 0; r < requires.length; ++ r)
                    {
                        if (opts.getOpt (requires [r]) == null)
                            opts.addError (formatMessage ("option \'" + opt.getName () + "\' requires option \'" + requires [r] +  "\'"));
                    }
                }
                
                // validation: "not with" constraints
                
                final String [] excludes = optdef.getExcludesSet (); // not canonicalized
                if (excludes != null)
                {
                    for (int x = 0; x < excludes.length; ++ x)
                    {
                        final Opt xopt = opts.getOpt (excludes [x]);
                        if (xopt != null)
                            opts.addError (formatMessage ("option \'" + opt.getName () + "\' cannot be used with option \'" + xopt.getName () +  "\'"));
                    }
                }
                
                // side effect: determine if usage is requested
                
                if (optdef.isUsage ())
                {
                    opts.setUsageRequested (opt.getName ().equals (opt.getCanonicalName ()) ? SHORT_USAGE : DETAILED_USAGE);
                }
            }
        }
        
        return opts;
    
private static com.vladium.util.args.OptsParser$OptDefMetadataparseOptDefMetadata(java.io.Reader in, java.lang.String[] usageOpts)

        
     // end of nested class
    
    
              
    
        final MetadataParser parser = new MetadataParser ();
        final OptDef [] optdefs = parser.parse (in);
        
        // validate:
        
//        for (int o = 0; o < optdefs.length; ++ o)
//        {
//            final OptDef optdef = optdefs [o];
//            final int [] cardinality = optdef.getValueCardinality ();
//            
//            if (optdef.isMergeable ())
//            {
//                if ((cardinality [1] != 0) && (cardinality [1] != Integer.MAX_VALUE))
//                    throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] is mergeable and can only specify {0, +inf} for max value cardinality: " + cardinality [1]); 
//            }            
//        } 
         
        final OptDefMetadata result = new OptDefMetadata ();
        for (int o = 0; o < optdefs.length; ++ o)
        {
            result.addOptDef (optdefs [o]);
        }
        
        // add usage opts:
        if (usageOpts != null)
        {
            final OptDef usage = new OptDef (true);
            
            usage.setNames (usageOpts);
            usage.setDescription ("display usage information");
            usage.setValueCardinality (OptDef.C_ZERO);
            usage.setRequired (false);
            usage.setDetailedOnly (false);
            usage.setMergeable (false);
            
            result.addOptDef (usage);
        }
        
        // TODO: fix this to be pattern-savvy
        
        for (int o = 0; o < optdefs.length; ++ o)
        {
            final OptDef optdef = optdefs [o];
            
            final String [] requires = optdef.getRequiresSet ();
            if (requires != null)
            {
                for (int r = 0; r < requires.length; ++ r)
                {
                    final OptDef ropt = result.getOptDef (requires [r], null);
                    if (ropt == null)
                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + requires [r] + "] in its \'requires\' set");
                    
                    if (ropt == optdef)
                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'requires\' set");
                }
            }
            
            final String [] excludes = optdef.getExcludesSet ();
            if (excludes != null)
            {
                for (int x = 0; x < excludes.length; ++ x)
                {
                    final OptDef xopt = result.getOptDef (excludes [x], null);
                    if (xopt == null)
                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + excludes [x] + "] in its \'excludes\' set");
                    
                    if (xopt.isRequired ())
                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies a required option [" + excludes [x] + "] in its \'excludes\' set");
                    
                    if (xopt == optdef)
                        throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'excludes\' set");
                }
            }
        }
        
        return result;
    
public synchronized voidusage(java.io.PrintWriter out, int level, int width)

        // TODO: use width
        // TODO: cache?
        
        final String prefix = OPT_PREFIXES [CANONICAL_OPT_PREFIX];
        
        for (Iterator i = m_metadata.getOptDefs (); i.hasNext (); )
        {
            final OptDef optdef = (OptDef) i.next ();
            
            if ((level < 2) && optdef.isDetailedOnly ()) // skip detailed usage only options 
                continue;
            
            final StringBuffer line = new StringBuffer ("  ");
            
            final String canonicalName = optdef.getCanonicalName ();
            final boolean isPattern = optdef.isPattern (); 
             
            line.append (prefix);
            line.append (canonicalName);
            if (isPattern) line.append ('*");
            
            final String [] names = optdef.getNames ();
            for (int n = 0; n < names.length; ++ n)
            {
                final String name = names [n];
                if (! name.equals (canonicalName))
                {
                    line.append (", ");
                    
                    line.append (prefix);
                    line.append (name);
                    if (isPattern) line.append ('*");
                }
            }

            final String vmnemonic = optdef.getValueMnemonic ();
            if (vmnemonic != null)
            {
                line.append (' ");
                line.append (vmnemonic);
            }

            
            int padding = 16 - line.length ();
            if (padding < 2)
            {
                // end the current line
                out.println (line);
                
                line.setLength (0);
                for (int p = 0; p < 16; ++ p) line.append (' ");
            }
            else
            {          
                for (int p = 0; p < padding; ++ p) line.append (' ");
            }
            
            if (optdef.isRequired ()) line.append ("{required} ");
            line.append (optdef.getDescription ());
            
            out.println (line);
        }
        
        if (level < DETAILED_USAGE)
        {
            final OptDef usageOptDef = m_metadata.getUsageOptDef ();
            if ((usageOptDef != null) && (usageOptDef.getNames () != null) && (usageOptDef.getNames ().length > 1))
            {
                out.println (); 
                out.println ("  {use '" + usageOptDef.getNames () [1] + "' option to see detailed usage help}");
            }
        }