    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     * @since 1.1
    122     */
    123    public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException {
    124        return parse(options, arguments, properties, false);
    125    }
    126
    127    /**
    128     * Parse the arguments according to the specified options and 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 {@code true} an unrecognized argument stops the parsing and the remaining arguments
    134     *        are added to the {@link CommandLine}s args list. If {@code false} an unrecognized argument triggers a
    135     *        ParseException.
    136     * @return the list of atomic option and value tokens
    137     * @throws ParseException if there are any problems encountered while parsing the command line tokens.
    138     * @since 1.1
    139     */
    140    public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption)
    141            throws ParseException {
    142        // clear out the data in options in case it's been used before (CLI-71)
    143        for (final Option opt : options.helpOptions()) {
    144            opt.clearValues();
    145        }
    146        // clear the data from the groups
    147        for (final OptionGroup group : options.getOptionGroups()) {
    148            group.setSelected(null);
    149        }
    150        // initialize members
    151        setOptions(options);
    152        cmd = CommandLine.builder().build();
    153        boolean eatTheRest = false;
    154        final List<String> tokenList = Arrays.asList(flatten(getOptions(), arguments == null ? new String[0] : arguments, stopAtNonOption));
    155        final ListIterator<String> iterator = tokenList.listIterator();
    156        // process each flattened token
    157        while (iterator.hasNext()) {
    158            final String token = iterator.next();
    159            if (token != null) {
    160                // the value is the double-dash
    161                if ("--".equals(token)) {
    162                    eatTheRest = true;
    163                } else if ("-".equals(token)) {
    164                    // the value is a single dash
    165                    if (stopAtNonOption) {
    166                        eatTheRest = true;
    167                    } else {
    168                        cmd.addArg(token);
    169                    }
    170                } else if (token.startsWith("-")) {
    171                    // the value is an option
    172                    if (stopAtNonOption && !getOptions().hasOption(token)) {
    173                        eatTheRest = true;
    174                        cmd.addArg(token);
    175                    } else {
    176                        processOption(token, iterator);
    177                    }
    178                } else {
    179                    // the value is an argument
    180                    cmd.addArg(token);
    181                    if (stopAtNonOption) {
    182                        eatTheRest = true;
    183                    }
    184                }
    185                // eat the remaining tokens
    186                if (eatTheRest) {
    187                    while (iterator.hasNext()) {
    188                        final String str = iterator.next();
    189                        // ensure only one double-dash is added
    190                        if (!"--".equals(str)) {
    191                            cmd.addArg(str);
    192                        }
    193                    }
    194                }
    195            }
    196        }
    197        processProperties(properties);
    198        checkRequiredOptions();
    199        return cmd;
    200    }
    201
    202    /**
    203     * Process the argument values for the specified Option {@code opt} using the values retrieved from the specified
    204     * iterator {@code iter}.
    205     *
    206     * @param opt The current Option
    207     * @param iter The iterator over the flattened command line Options.
    208     * @throws ParseException if an argument value is required and it is has not been found.
    209     */
    210    public void processArgs(final Option opt, final ListIterator<String> iter) throws ParseException {
    211        // loop until an option is found
    212        while (iter.hasNext()) {
    213            final String str = iter.next();
    214            // found an Option, not an argument
    215            if (getOptions().hasOption(str) && str.startsWith("-")) {
    216                iter.previous();
    217                break;
    218            }
    219            // found a value
    220            try {
    221                opt.processValue(Util.stripLeadingAndTrailingQuotes(str));
    222            } catch (final RuntimeException exp) {
    223                iter.previous();
    224                break;
    225            }
    226        }
    227        if (opt.getValues() == null && !opt.hasOptionalArg()) {
    228            throw new MissingArgumentException(opt);
    229        }
    230    }
    231
    232    /**
    233     * Process the Option specified by {@code arg} using the values retrieved from the specified iterator
    234     * {@code iter}.
    235     *
    236     * @param arg The String value representing an Option
    237     * @param iter The iterator over the flattened command line arguments.
    238     * @throws ParseException if {@code arg} does not represent an Option
    239     */
    240    protected void processOption(final String arg, final ListIterator<String> iter) throws ParseException {
    241        final boolean hasOption = getOptions().hasOption(arg);
    242        // if there is no option throw an UnrecognizedOptionException
    243        if (!hasOption) {
    244            throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
    245        }
    246        // get the option represented by arg
    247        final Option opt = (Option) getOptions().getOption(arg).clone();
    248        // update the required options and groups
    249        updateRequiredOptions(opt);
    250        // if the option takes an argument value
    251        if (opt.hasArg()) {
    252            processArgs(opt, iter);
    253        }
    254        // set the option on the command line
    255        cmd.addOption(opt);
    256    }
    257
    258    /**
    259     * Sets the values of Options using the values in {@code properties}.
    260     *
    261     * @param properties The value properties to be processed.
    262     * @throws ParseException if there are any problems encountered while processing the properties.
    263     */
    264    protected void processProperties(final Properties properties) throws ParseException {
    265        if (properties == null) {
    266            return;
    267        }
    268        for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
    269            final String option = e.nextElement().toString();
    270            final Option opt = options.getOption(option);
    271            if (opt == null) {
    272                throw new UnrecognizedOptionException("Default option wasn't defined", option);
    273            }
    274            // if the option is part of a group, check if another option of the group has been selected
    275            final OptionGroup group = options.getOptionGroup(opt);
    276            final boolean selected = group != null && group.isSelected();
    277            if (!cmd.hasOption(option) && !selected) {
    278                // get the value from the properties instance
    279                final String value = properties.getProperty(option);
    280                if (opt.hasArg()) {
    281                    if (Util.isEmpty(opt.getValues())) {
    282                        try {
    283                            opt.processValue(value);
    284                        } catch (final RuntimeException exp) { // NOPMD
    285                            // if we cannot add the value don't worry about it
    286                        }
    287                    }
    288                } else if (!("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value))) {
    289                    // if the value is not yes, true or 1 then don't add the
    290                    // option to the CommandLine
    291                    continue;
    292                }
    293                cmd.addOption(opt);
    294                updateRequiredOptions(opt);
    295            }
    296        }
    297    }
    298
    299    /**
    300     * Sets the options.
    301     *
    302     * @param options the options.
    303     */
    304    protected void setOptions(final Options options) {
    305        this.options = options;
    306        this.requiredOptions = new ArrayList<>(options.getRequiredOptions());
    307    }
    308
    309    /**
    310     * Removes the option or its group from the list of expected elements.
    311     *
    312     * @param opt
    313     */
    314    private void updateRequiredOptions(final Option opt) throws ParseException {
    315        // if the option is a required option remove the option from
    316        // the requiredOptions list
    317        if (opt.isRequired()) {
    318            getRequiredOptions().remove(opt.getKey());
    319        }
    320        // if the option is in an OptionGroup make that option the selected
    321        // option of the group
    322        if (getOptions().getOptionGroup(opt) != null) {
    323            final OptionGroup group = getOptions().getOptionGroup(opt);
    324            if (group.isRequired()) {
    325                getRequiredOptions().remove(group);
    326            }
    327            group.setSelected(opt);
    328        }
    329    }
    330
    331}