    001    /**
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.cli;
    019
    020    import java.util.ArrayList;
    021    import java.util.Arrays;
    022    import java.util.Enumeration;
    023    import java.util.Iterator;
    024    import java.util.List;
    025    import java.util.ListIterator;
    026    import java.util.Properties;
    027
    028    /**
    029     * <code>Parser</code> creates {@link CommandLine}s.
    030     *
    031     * @author John Keyes (john at integralsource.com)
    032     * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $
    033     */
    034    public abstract class Parser implements CommandLineParser
    035    {
    036        /** commandline instance */
    037        protected CommandLine cmd;
    038
    039        /** current Options */
    040        private Options options;
    041
    042        /** list of required options strings */
    043        private List requiredOptions;
    044
    045        protected void setOptions(final Options options)
    046        {
    047            this.options = options;
    048            this.requiredOptions = new ArrayList(options.getRequiredOptions());
    049        }
    050
    051        protected Options getOptions()
    052        {
    053            return options;
    054        }
    055
    056        protected List getRequiredOptions()
    057        {
    058            return requiredOptions;
    059        }
    060
    061        /**
    062         * Subclasses must implement this method to reduce
    063         * the <code>arguments</code> that have been passed to the parse method.
    064         *
    065         * @param opts The Options to parse the arguments by.
    066         * @param arguments The arguments that have to be flattened.
    067         * @param stopAtNonOption specifies whether to stop
    068         * flattening when a non option has been encountered
    069         * @return a String array of the flattened arguments
    070         */
    071        protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption);
    072
    073        /**
    074         * Parses the specified <code>arguments</code> based
    075         * on the specifed {@link Options}.
    076         *
    077         * @param options the <code>Options</code>
    078         * @param arguments the <code>arguments</code>
    079         * @return the <code>CommandLine</code>
    080         * @throws ParseException if an error occurs when parsing the
    081         * arguments.
    082         */
    083        public CommandLine parse(Options options, String[] arguments) throws ParseException
    084        {
    085            return parse(options, arguments, null, false);
    086        }
    087
    088        /**
    089         * Parse the arguments according to the specified options and properties.
    090         *
    091         * @param options    the specified Options
    092         * @param arguments  the command line arguments
    093         * @param properties command line option name-value pairs
    094         * @return the list of atomic option and value tokens
    095         * @throws ParseException if there are any problems encountered
    096         *                        while parsing the command line tokens.
    097         *
    098         * @since 1.1
    099         */
    100        public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException
    101        {
    102            return parse(options, arguments, properties, false);
    103        }
    104
    105        /**
    106         * Parses the specified <code>arguments</code>
    107         * based on the specifed {@link Options}.
    108         *
    109         * @param options         the <code>Options</code>
    110         * @param arguments       the <code>arguments</code>
    111         * @param stopAtNonOption specifies whether to stop interpreting the
    112         *                        arguments when a non option has been encountered
    113         *                        and to add them to the CommandLines args list.
    114         * @return the <code>CommandLine</code>
    115         * @throws ParseException if an error occurs when parsing the arguments.
    116         */
    117        public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
    118        {
    119            return parse(options, arguments, null, stopAtNonOption);
    120        }
    121
    122        /**
    123         * Parse the arguments according to the specified options and
    124         * properties.
    125         *
    126         * @param options the specified Options
    127         * @param arguments the command line arguments
    128         * @param properties command line option name-value pairs
    129         * @param stopAtNonOption stop parsing the arguments when the first
    130         * non option is encountered.
    131         *
    132         * @return the list of atomic option and value tokens
    133         *
    134         * @throws ParseException if there are any problems encountered
    135         * while parsing the command line tokens.
    136         *
    137         * @since 1.1
    138         */
    139        public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
    140                throws ParseException
    141        {
    142            // clear out the data in options in case it's been used before (CLI-71)
    143            for (Iterator it = options.helpOptions().iterator(); it.hasNext();)
    144            {
    145                Option opt = (Option) it.next();
    146                opt.clearValues();
    147            }
    148
    149            // initialise members
    150            setOptions(options);
    151
    152            cmd = new CommandLine();
    153
    154            boolean eatTheRest = false;
    155
    156            if (arguments == null)
    157            {
    158                arguments = new String[0];
    159            }
    160
    161            List tokenList = Arrays.asList(flatten(getOptions(), arguments, stopAtNonOption));
    162
    163            ListIterator iterator = tokenList.listIterator();
    164
    165            // process each flattened token
    166            while (iterator.hasNext())
    167            {
    168                String t = (String) iterator.next();
    169
    170                // the value is the double-dash
    171                if ("--".equals(t))
    172                {
    173                    eatTheRest = true;
    174                }
    175
    176                // the value is a single dash
    177                else if ("-".equals(t))
    178                {
    179                    if (stopAtNonOption)
    180                    {
    181                        eatTheRest = true;
    182                    }
    183                    else
    184                    {
    185                        cmd.addArg(t);
    186                    }
    187                }
    188
    189                // the value is an option
    190                else if (t.startsWith("-"))
    191                {
    192                    if (stopAtNonOption && !getOptions().hasOption(t))
    193                    {
    194                        eatTheRest = true;
    195                        cmd.addArg(t);
    196                    }
    197                    else
    198                    {
    199                        processOption(t, iterator);
    200                    }
    201                }
    202
    203                // the value is an argument
    204                else
    205                {
    206                    cmd.addArg(t);
    207
    208                    if (stopAtNonOption)
    209                    {
    210                        eatTheRest = true;
    211                    }
    212                }
    213
    214                // eat the remaining tokens
    215                if (eatTheRest)
    216                {
    217                    while (iterator.hasNext())
    218                    {
    219                        String str = (String) iterator.next();
    220
    221                        // ensure only one double-dash is added
    222                        if (!"--".equals(str))
    223                        {
    224                            cmd.addArg(str);
    225                        }
    226                    }
    227                }
    228            }
    229
    230            processProperties(properties);
    231            checkRequiredOptions();
    232
    233            return cmd;
    234        }
    235
    236        /**
    237         * Sets the values of Options using the values in <code>properties</code>.
    238         *
    239         * @param properties The value properties to be processed.
    240         */
    241        protected void processProperties(Properties properties)
    242        {
    243            if (properties == null)
    244            {
    245                return;
    246            }
    247
    248            for (Enumeration e = properties.propertyNames(); e.hasMoreElements();)
    249            {
    250                String option = e.nextElement().toString();
    251
    252                if (!cmd.hasOption(option))
    253                {
    254                    Option opt = getOptions().getOption(option);
    255
    256                    // get the value from the properties instance
    257                    String value = properties.getProperty(option);
    258
    259                    if (opt.hasArg())
    260                    {
    261                        if (opt.getValues() == null || opt.getValues().length == 0)
    262                        {
    263                            try
    264                            {
    265                                opt.addValueForProcessing(value);
    266                            }
    267                            catch (RuntimeException exp)
    268                            {
    269                                // if we cannot add the value don't worry about it
    270                            }
    271                        }
    272                    }
    273                    else if (!("yes".equalsIgnoreCase(value)
    274                            || "true".equalsIgnoreCase(value)
    275                            || "1".equalsIgnoreCase(value)))
    276                    {
    277                        // if the value is not yes, true or 1 then don't add the
    278                        // option to the CommandLine
    279                        break;
    280                    }
    281
    282                    cmd.addOption(opt);
    283                }
    284            }
    285        }
    286
    287        /**
    288         * Throws a {@link MissingOptionException} if all of the required options
    289         * are not present.
    290         *
    291         * @throws MissingOptionException if any of the required Options
    292         * are not present.
    293         */
    294        protected void checkRequiredOptions() throws MissingOptionException
    295        {
    296            // if there are required options that have not been processsed
    297            if (!getRequiredOptions().isEmpty())
    298            {
    299                throw new MissingOptionException(getRequiredOptions());
    300            }
    301        }
    302
    303        /**
    304         * <p>Process the argument values for the specified Option
    305         * <code>opt</code> using the values retrieved from the
    306         * specified iterator <code>iter</code>.
    307         *
    308         * @param opt The current Option
    309         * @param iter The iterator over the flattened command line
    310         * Options.
    311         *
    312         * @throws ParseException if an argument value is required
    313         * and it is has not been found.
    314         */
    315        public void processArgs(Option opt, ListIterator iter) throws ParseException
    316        {
    317            // loop until an option is found
    318            while (iter.hasNext())
    319            {
    320                String str = (String) iter.next();
    321
    322                // found an Option, not an argument
    323                if (getOptions().hasOption(str) && str.startsWith("-"))
    324                {
    325                    iter.previous();
    326                    break;
    327                }
    328
    329                // found a value
    330                try
    331                {
    332                    opt.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(str));
    333                }
    334                catch (RuntimeException exp)
    335                {
    336                    iter.previous();
    337                    break;
    338                }
    339            }
    340
    341            if (opt.getValues() == null && !opt.hasOptionalArg())
    342            {
    343                throw new MissingArgumentException(opt);
    344            }
    345        }
    346
    347        /**
    348         * Process the Option specified by <code>arg</code> using the values
    349         * retrieved from the specfied iterator <code>iter</code>.
    350         *
    351         * @param arg The String value representing an Option
    352         * @param iter The iterator over the flattened command line arguments.
    353         *
    354         * @throws ParseException if <code>arg</code> does not represent an Option
    355         */
    356        protected void processOption(String arg, ListIterator iter) throws ParseException
    357        {
    358            boolean hasOption = getOptions().hasOption(arg);
    359
    360            // if there is no option throw an UnrecognisedOptionException
    361            if (!hasOption)
    362            {
    363                throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
    364            }
    365
    366            // get the option represented by arg
    367            Option opt = (Option) getOptions().getOption(arg).clone();
    368
    369            // if the option is a required option remove the option from
    370            // the requiredOptions list
    371            if (opt.isRequired())
    372            {
    373                getRequiredOptions().remove(opt.getKey());
    374            }
    375
    376            // if the option is in an OptionGroup make that option the selected
    377            // option of the group
    378            if (getOptions().getOptionGroup(opt) != null)
    379            {
    380                OptionGroup group = getOptions().getOptionGroup(opt);
    381
    382                if (group.isRequired())
    383                {
    384                    getRequiredOptions().remove(group);
    385                }
    386
    387                group.setSelected(opt);
    388            }
    389
    390            // if the option takes an argument value
    391            if (opt.hasArg())
    392            {
    393                processArgs(opt, iter);
    394            }
    395
    396            // set the option on the command line
    397            cmd.addOption(opt);
    398        }
    399    }