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