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    }