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 }