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