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
018package org.apache.commons.cli;
019
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Enumeration;
023import java.util.Iterator;
024import java.util.List;
025import java.util.ListIterator;
026import 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 */
034public 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}