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