    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.Iterator;
    023import java.util.List;
    024
    025/**
    026 * The class PosixParser provides an implementation of the {@link Parser#flatten(Options,String[],boolean) flatten}
    027 * method.
    028 *
    029 * @deprecated since 1.3, use the {@link DefaultParser} instead
    030 */
    031@Deprecated
    032public class PosixParser extends Parser {
    033    /** Holder for flattened tokens */
    034    private final List<String> tokens = new ArrayList<>();
    035
    036    /** Specifies if bursting should continue */
    037    private boolean eatTheRest;
    038
    039    /** Holder for the current option */
    040    private Option currentOption;
    041
    042    /** The command line Options */
    043    private Options options;
    044
    045    /**
    046     * Breaks {@code token} into its constituent parts using the following algorithm.
    047     *
    048     * <ul>
    049     * <li>ignore the first character ("<b>-</b>")</li>
    050     * <li>for each remaining character check if an {@link Option} exists with that id.</li>
    051     * <li>if an {@link Option} does exist then add that character prepended with "<b>-</b>" to the list of processed
    052     * tokens.</li>
    053     * <li>if the {@link Option} can have an argument value and there are remaining characters in the token then add the
    054     * remaining characters as a token to the list of processed tokens.</li>
    055     * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> {@code stopAtNonOption} <b>IS</b> set then add the
    056     * special token "<b>--</b>" followed by the remaining characters and also the remaining tokens directly to the
    057     * processed tokens list.</li>
    058     * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> {@code stopAtNonOption} <b>IS NOT</b> set then add
    059     * that character prepended with "<b>-</b>".</li>
    060     * </ul>
    061     *
    062     * @param token The current token to be <b>burst</b>
    063     * @param stopAtNonOption Specifies whether to stop processing at the first non-Option encountered.
    064     */
    065    protected void burstToken(final String token, final boolean stopAtNonOption) {
    066        for (int i = 1; i < token.length(); i++) {
    067            final String ch = String.valueOf(token.charAt(i));
    068
    069            if (!options.hasOption(ch)) {
    070                if (stopAtNonOption) {
    071                    processNonOptionToken(token.substring(i), true);
    072                } else {
    073                    tokens.add(token);
    074                }
    075                break;
    076            }
    077            tokens.add("-" + ch);
    078            currentOption = options.getOption(ch);
    079
    080            if (currentOption.hasArg() && token.length() != i + 1) {
    081                tokens.add(token.substring(i + 1));
    082
    083                break;
    084            }
    085        }
    086    }
    087
    088    /**
    089     * <p>
    090     * An implementation of {@link Parser}'s abstract {@link Parser#flatten(Options,String[],boolean) flatten} method.
    091     * </p>
    092     *
    093     * <p>
    094     * The following are the rules used by this flatten method.
    095     * </p>
    096     * <ol>
    097     * <li>if {@code stopAtNonOption} is <b>true</b> then do not burst anymore of {@code arguments} entries, just
    098     * add each successive entry without further processing. Otherwise, ignore {@code stopAtNonOption}.</li>
    099     * <li>if the current {@code arguments} entry is "<b>--</b>" just add the entry to the list of processed
    100     * tokens</li>
    101     * <li>if the current {@code arguments} entry is "<b>-</b>" just add the entry to the list of processed tokens</li>
    102     * <li>if the current {@code arguments} entry is two characters in length and the first character is "<b>-</b>"
    103     * then check if this is a valid {@link Option} id. If it is a valid id, then add the entry to the list of processed
    104     * tokens and set the current {@link Option} member. If it is not a valid id and {@code stopAtNonOption} is true,
    105     * then the remaining entries are copied to the list of processed tokens. Otherwise, the current entry is ignored.</li>
    106     * <li>if the current {@code arguments} entry is more than two characters in length and the first character is
    107     * "<b>-</b>" then we need to burst the entry to determine its constituents. For more information on the bursting
    108     * algorithm see {@link PosixParser#burstToken(String, boolean) burstToken}.</li>
    109     * <li>if the current {@code arguments} entry is not handled by any of the previous rules, then the entry is added
    110     * to the list of processed tokens.</li>
    111     * </ol>
    112     *
    113     * @param options The command line {@link Options}
    114     * @param arguments The command line arguments to be parsed
    115     * @param stopAtNonOption Specifies whether to stop flattening when an non option is found.
    116     * @return The flattened {@code arguments} String array.
    117     */
    118    @Override
    119    protected String[] flatten(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException {
    120        init();
    121        this.options = options;
    122
    123        // an iterator for the command line tokens
    124        final Iterator<String> iter = Arrays.asList(arguments).iterator();
    125
    126        // process each command line token
    127        while (iter.hasNext()) {
    128            // get the next command line token
    129            final String token = iter.next();
    130
    131            // single or double hyphen
    132            if ("-".equals(token) || "--".equals(token)) {
    133                tokens.add(token);
    134            }
    135
    136            // handle long option --foo or --foo=bar
    137            else if (token.startsWith("--")) {
    138                final int pos = DefaultParser.indexOfEqual(token);
    139                final String opt = pos == -1 ? token : token.substring(0, pos); // --foo
    140
    141                final List<String> matchingOpts = options.getMatchingOptions(opt);
    142
    143                if (matchingOpts.isEmpty()) {
    144                    processNonOptionToken(token, stopAtNonOption);
    145                } else if (matchingOpts.size() > 1) {
    146                    throw new AmbiguousOptionException(opt, matchingOpts);
    147                } else {
    148                    currentOption = options.getOption(matchingOpts.get(0));
    149
    150                    tokens.add("--" + currentOption.getLongOpt());
    151                    if (pos != -1) {
    152                        tokens.add(token.substring(pos + 1));
    153                    }
    154                }
    155            }
    156
    157            else if (token.startsWith("-")) {
    158                if (token.length() == 2 || options.hasOption(token)) {
    159                    processOptionToken(token, stopAtNonOption);
    160                } else if (!options.getMatchingOptions(token).isEmpty()) {
    161                    final List<String> matchingOpts = options.getMatchingOptions(token);
    162                    if (matchingOpts.size() > 1) {
    163                        throw new AmbiguousOptionException(token, matchingOpts);
    164                    }
    165                    final Option opt = options.getOption(matchingOpts.get(0));
    166                    processOptionToken("-" + opt.getLongOpt(), stopAtNonOption);
    167                }
    168                // requires bursting
    169                else {
    170                    burstToken(token, stopAtNonOption);
    171                }
    172            } else {
    173                processNonOptionToken(token, stopAtNonOption);
    174            }
    175
    176            gobble(iter);
    177        }
    178
    179        return tokens.toArray(Util.EMPTY_STRING_ARRAY);
    180    }
    181
    182    /**
    183     * Adds the remaining tokens to the processed tokens list.
    184     *
    185     * @param iter An iterator over the remaining tokens
    186     */
    187    private void gobble(final Iterator<String> iter) {
    188        if (eatTheRest) {
    189            while (iter.hasNext()) {
    190                tokens.add(iter.next());
    191            }
    192        }
    193    }
    194
    195    /**
    196     * Resets the members to their original state i.e. remove all of {@code tokens} entries and set
    197     * {@code eatTheRest} to false.
    198     */
    199    private void init() {
    200        eatTheRest = false;
    201        tokens.clear();
    202    }
    203
    204    /**
    205     * Add the special token "<b>--</b>" and the current {@code value} to the processed tokens list. Then add all the
    206     * remaining {@code argument} values to the processed tokens list.
    207     *
    208     * @param value The current token
    209     */
    210    private void processNonOptionToken(final String value, final boolean stopAtNonOption) {
    211        if (stopAtNonOption && (currentOption == null || !currentOption.hasArg())) {
    212            eatTheRest = true;
    213            tokens.add("--");
    214        }
    215
    216        tokens.add(value);
    217    }
    218
    219    /**
    220     * <p>
    221     * If an {@link Option} exists for {@code token} then add the token to the processed list.
    222     * </p>
    223     *
    224     * <p>
    225     * If an {@link Option} does not exist and {@code stopAtNonOption} is set then add the remaining tokens to the
    226     * processed tokens list directly.
    227     * </p>
    228     *
    229     * @param token The current option token
    230     * @param stopAtNonOption Specifies whether flattening should halt at the first non option.
    231     */
    232    private void processOptionToken(final String token, final boolean stopAtNonOption) {
    233        if (stopAtNonOption && !options.hasOption(token)) {
    234            eatTheRest = true;
    235        }
    236
    237        if (options.hasOption(token)) {
    238            currentOption = options.getOption(token);
    239        }
    240
    241        tokens.add(token);
    242    }
    243}