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