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
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 */
032public 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}