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.List;
024import java.util.ListIterator;
025import java.util.Properties;
026
027/**
028 * <code>Parser</code> creates {@link CommandLine}s.
029 *
030 * @version $Id: Parser.java 1677406 2015-05-03 14:27:31Z britter $
031 * @deprecated since 1.3, the two-pass parsing with the flatten method is not enough flexible to handle complex cases
032 */
033@Deprecated
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(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     * @throws ParseException if there are any problems encountered
071     *                        while parsing the command line tokens.
072     */
073    protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption)
074            throws ParseException;
075
076    /**
077     * Parses the specified <code>arguments</code> based
078     * on the specified {@link Options}.
079     *
080     * @param options the <code>Options</code>
081     * @param arguments the <code>arguments</code>
082     * @return the <code>CommandLine</code>
083     * @throws ParseException if there are any problems encountered
084     *                        while parsing the command line tokens.
085     */
086    public CommandLine parse(Options options, String[] arguments) throws ParseException
087    {
088        return parse(options, arguments, null, false);
089    }
090
091    /**
092     * Parse the arguments according to the specified options and properties.
093     *
094     * @param options    the specified Options
095     * @param arguments  the command line arguments
096     * @param properties command line option name-value pairs
097     * @return the list of atomic option and value tokens
098     * @throws ParseException if there are any problems encountered
099     *                        while parsing the command line tokens.
100     *
101     * @since 1.1
102     */
103    public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException
104    {
105        return parse(options, arguments, properties, false);
106    }
107
108    /**
109     * Parses the specified <code>arguments</code>
110     * based on the specified {@link Options}.
111     *
112     * @param options         the <code>Options</code>
113     * @param arguments       the <code>arguments</code>
114     * @param stopAtNonOption if <tt>true</tt> an unrecognized argument stops
115     *     the parsing and the remaining arguments are added to the 
116     *     {@link CommandLine}s args list. If <tt>false</tt> an unrecognized
117     *     argument triggers a ParseException.
118     * @return the <code>CommandLine</code>
119     * @throws ParseException if an error occurs when parsing the arguments.
120     */
121    public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
122    {
123        return parse(options, arguments, null, stopAtNonOption);
124    }
125
126    /**
127     * Parse the arguments according to the specified options and
128     * properties.
129     *
130     * @param options the specified Options
131     * @param arguments the command line arguments
132     * @param properties command line option name-value pairs
133     * @param stopAtNonOption if <tt>true</tt> an unrecognized argument stops
134     *     the parsing and the remaining arguments are added to the 
135     *     {@link CommandLine}s args list. If <tt>false</tt> an unrecognized
136     *     argument triggers a ParseException.
137     *
138     * @return the list of atomic option and value tokens
139     *
140     * @throws ParseException if there are any problems encountered
141     * while parsing the command line tokens.
142     *
143     * @since 1.1
144     */
145    public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
146            throws ParseException
147    {
148        // clear out the data in options in case it's been used before (CLI-71)
149        for (Option opt : options.helpOptions())
150        {
151            opt.clearValues();
152        }
153        
154        // clear the data from the groups
155        for (OptionGroup group : options.getOptionGroups())
156        {
157            group.setSelected(null);
158        }        
159
160        // initialise members
161        setOptions(options);
162
163        cmd = new CommandLine();
164
165        boolean eatTheRest = false;
166
167        if (arguments == null)
168        {
169            arguments = new String[0];
170        }
171
172        List<String> tokenList = Arrays.asList(flatten(getOptions(), arguments, stopAtNonOption));
173
174        ListIterator<String> iterator = tokenList.listIterator();
175
176        // process each flattened token
177        while (iterator.hasNext())
178        {
179            String t = iterator.next();
180
181            // the value is the double-dash
182            if ("--".equals(t))
183            {
184                eatTheRest = true;
185            }
186
187            // the value is a single dash
188            else if ("-".equals(t))
189            {
190                if (stopAtNonOption)
191                {
192                    eatTheRest = true;
193                }
194                else
195                {
196                    cmd.addArg(t);
197                }
198            }
199
200            // the value is an option
201            else if (t.startsWith("-"))
202            {
203                if (stopAtNonOption && !getOptions().hasOption(t))
204                {
205                    eatTheRest = true;
206                    cmd.addArg(t);
207                }
208                else
209                {
210                    processOption(t, iterator);
211                }
212            }
213
214            // the value is an argument
215            else
216            {
217                cmd.addArg(t);
218
219                if (stopAtNonOption)
220                {
221                    eatTheRest = true;
222                }
223            }
224
225            // eat the remaining tokens
226            if (eatTheRest)
227            {
228                while (iterator.hasNext())
229                {
230                    String str = iterator.next();
231
232                    // ensure only one double-dash is added
233                    if (!"--".equals(str))
234                    {
235                        cmd.addArg(str);
236                    }
237                }
238            }
239        }
240
241        processProperties(properties);
242        checkRequiredOptions();
243
244        return cmd;
245    }
246
247    /**
248     * Sets the values of Options using the values in <code>properties</code>.
249     *
250     * @param properties The value properties to be processed.
251     * @throws ParseException if there are any problems encountered
252     *                        while processing the properties.
253     */
254    protected void processProperties(Properties properties) throws ParseException
255    {
256        if (properties == null)
257        {
258            return;
259        }
260
261        for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();)
262        {
263            String option = e.nextElement().toString();
264            
265            Option opt = options.getOption(option);
266            if (opt == null)
267            {
268                throw new UnrecognizedOptionException("Default option wasn't defined", option);
269            }
270            
271            // if the option is part of a group, check if another option of the group has been selected
272            OptionGroup group = options.getOptionGroup(opt);
273            boolean selected = group != null && group.getSelected() != null;
274            
275            if (!cmd.hasOption(option) && !selected)
276            {
277                // get the value from the properties instance
278                String value = properties.getProperty(option);
279
280                if (opt.hasArg())
281                {
282                    if (opt.getValues() == null || opt.getValues().length == 0)
283                    {
284                        try
285                        {
286                            opt.addValueForProcessing(value);
287                        }
288                        catch (RuntimeException exp) //NOPMD
289                        {
290                            // if we cannot add the value don't worry about it
291                        }
292                    }
293                }
294                else if (!("yes".equalsIgnoreCase(value)
295                        || "true".equalsIgnoreCase(value)
296                        || "1".equalsIgnoreCase(value)))
297                {
298                    // if the value is not yes, true or 1 then don't add the
299                    // option to the CommandLine
300                    continue;
301                }
302
303                cmd.addOption(opt);
304                updateRequiredOptions(opt);
305            }
306        }
307    }
308
309    /**
310     * Throws a {@link MissingOptionException} if all of the required options
311     * are not present.
312     *
313     * @throws MissingOptionException if any of the required Options are not present.
314     */
315    protected void checkRequiredOptions() throws MissingOptionException
316    {
317        // if there are required options that have not been processed
318        if (!getRequiredOptions().isEmpty())
319        {
320            throw new MissingOptionException(getRequiredOptions());
321        }
322    }
323
324    /**
325     * Process the argument values for the specified Option
326     * <code>opt</code> using the values retrieved from the
327     * specified iterator <code>iter</code>.
328     *
329     * @param opt The current Option
330     * @param iter The iterator over the flattened command line Options.
331     *
332     * @throws ParseException if an argument value is required
333     * and it is has not been found.
334     */
335    public void processArgs(Option opt, ListIterator<String> iter) throws ParseException
336    {
337        // loop until an option is found
338        while (iter.hasNext())
339        {
340            String str = iter.next();
341            
342            // found an Option, not an argument
343            if (getOptions().hasOption(str) && str.startsWith("-"))
344            {
345                iter.previous();
346                break;
347            }
348
349            // found a value
350            try
351            {
352                opt.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(str));
353            }
354            catch (RuntimeException exp)
355            {
356                iter.previous();
357                break;
358            }
359        }
360
361        if (opt.getValues() == null && !opt.hasOptionalArg())
362        {
363            throw new MissingArgumentException(opt);
364        }
365    }
366
367    /**
368     * Process the Option specified by <code>arg</code> using the values
369     * retrieved from the specified iterator <code>iter</code>.
370     *
371     * @param arg The String value representing an Option
372     * @param iter The iterator over the flattened command line arguments.
373     *
374     * @throws ParseException if <code>arg</code> does not represent an Option
375     */
376    protected void processOption(String arg, ListIterator<String> iter) throws ParseException
377    {
378        boolean hasOption = getOptions().hasOption(arg);
379
380        // if there is no option throw an UnrecognisedOptionException
381        if (!hasOption)
382        {
383            throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
384        }
385
386        // get the option represented by arg
387        Option opt = (Option) getOptions().getOption(arg).clone();
388        
389        // update the required options and groups
390        updateRequiredOptions(opt);
391        
392        // if the option takes an argument value
393        if (opt.hasArg())
394        {
395            processArgs(opt, iter);
396        }
397        
398        // set the option on the command line
399        cmd.addOption(opt);
400    }
401
402    /**
403     * Removes the option or its group from the list of expected elements.
404     * 
405     * @param opt
406     */
407    private void updateRequiredOptions(Option opt) throws ParseException
408    {
409        // if the option is a required option remove the option from
410        // the requiredOptions list
411        if (opt.isRequired())
412        {
413            getRequiredOptions().remove(opt.getKey());
414        }
415
416        // if the option is in an OptionGroup make that option the selected
417        // option of the group
418        if (getOptions().getOptionGroup(opt) != null)
419        {
420            OptionGroup group = getOptions().getOptionGroup(opt);
421
422            if (group.isRequired())
423            {
424                getRequiredOptions().remove(group);
425            }
426
427            group.setSelected(opt);
428        }
429    }
430}