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.Enumeration;
023 import java.util.Iterator;
024 import java.util.List;
025 import java.util.ListIterator;
026 import java.util.Properties;
027
028 /**
029 * <code>Parser</code> creates {@link CommandLine}s.
030 *
031 * @author John Keyes (john at integralsource.com)
032 * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $
033 */
034 public abstract class Parser implements CommandLineParser
035 {
036 /** commandline instance */
037 protected CommandLine cmd;
038
039 /** current Options */
040 private Options options;
041
042 /** list of required options strings */
043 private List requiredOptions;
044
045 protected void setOptions(final Options options)
046 {
047 this.options = options;
048 this.requiredOptions = new ArrayList(options.getRequiredOptions());
049 }
050
051 protected Options getOptions()
052 {
053 return options;
054 }
055
056 protected List getRequiredOptions()
057 {
058 return requiredOptions;
059 }
060
061 /**
062 * Subclasses must implement this method to reduce
063 * the <code>arguments</code> that have been passed to the parse method.
064 *
065 * @param opts The Options to parse the arguments by.
066 * @param arguments The arguments that have to be flattened.
067 * @param stopAtNonOption specifies whether to stop
068 * flattening when a non option has been encountered
069 * @return a String array of the flattened arguments
070 */
071 protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption);
072
073 /**
074 * Parses the specified <code>arguments</code> based
075 * on the specifed {@link Options}.
076 *
077 * @param options the <code>Options</code>
078 * @param arguments the <code>arguments</code>
079 * @return the <code>CommandLine</code>
080 * @throws ParseException if an error occurs when parsing the
081 * arguments.
082 */
083 public CommandLine parse(Options options, String[] arguments) throws ParseException
084 {
085 return parse(options, arguments, null, false);
086 }
087
088 /**
089 * Parse the arguments according to the specified options and properties.
090 *
091 * @param options the specified Options
092 * @param arguments the command line arguments
093 * @param properties command line option name-value pairs
094 * @return the list of atomic option and value tokens
095 * @throws ParseException if there are any problems encountered
096 * while parsing the command line tokens.
097 *
098 * @since 1.1
099 */
100 public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException
101 {
102 return parse(options, arguments, properties, false);
103 }
104
105 /**
106 * Parses the specified <code>arguments</code>
107 * based on the specifed {@link Options}.
108 *
109 * @param options the <code>Options</code>
110 * @param arguments the <code>arguments</code>
111 * @param stopAtNonOption specifies whether to stop interpreting the
112 * arguments when a non option has been encountered
113 * and to add them to the CommandLines args list.
114 * @return the <code>CommandLine</code>
115 * @throws ParseException if an error occurs when parsing the arguments.
116 */
117 public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
118 {
119 return parse(options, arguments, null, stopAtNonOption);
120 }
121
122 /**
123 * Parse the arguments according to the specified options and
124 * properties.
125 *
126 * @param options the specified Options
127 * @param arguments the command line arguments
128 * @param properties command line option name-value pairs
129 * @param stopAtNonOption stop parsing the arguments when the first
130 * non option is encountered.
131 *
132 * @return the list of atomic option and value tokens
133 *
134 * @throws ParseException if there are any problems encountered
135 * while parsing the command line tokens.
136 *
137 * @since 1.1
138 */
139 public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
140 throws ParseException
141 {
142 // clear out the data in options in case it's been used before (CLI-71)
143 for (Iterator it = options.helpOptions().iterator(); it.hasNext();)
144 {
145 Option opt = (Option) it.next();
146 opt.clearValues();
147 }
148
149 // initialise members
150 setOptions(options);
151
152 cmd = new CommandLine();
153
154 boolean eatTheRest = false;
155
156 if (arguments == null)
157 {
158 arguments = new String[0];
159 }
160
161 List tokenList = Arrays.asList(flatten(getOptions(), arguments, stopAtNonOption));
162
163 ListIterator iterator = tokenList.listIterator();
164
165 // process each flattened token
166 while (iterator.hasNext())
167 {
168 String t = (String) iterator.next();
169
170 // the value is the double-dash
171 if ("--".equals(t))
172 {
173 eatTheRest = true;
174 }
175
176 // the value is a single dash
177 else if ("-".equals(t))
178 {
179 if (stopAtNonOption)
180 {
181 eatTheRest = true;
182 }
183 else
184 {
185 cmd.addArg(t);
186 }
187 }
188
189 // the value is an option
190 else if (t.startsWith("-"))
191 {
192 if (stopAtNonOption && !getOptions().hasOption(t))
193 {
194 eatTheRest = true;
195 cmd.addArg(t);
196 }
197 else
198 {
199 processOption(t, iterator);
200 }
201 }
202
203 // the value is an argument
204 else
205 {
206 cmd.addArg(t);
207
208 if (stopAtNonOption)
209 {
210 eatTheRest = true;
211 }
212 }
213
214 // eat the remaining tokens
215 if (eatTheRest)
216 {
217 while (iterator.hasNext())
218 {
219 String str = (String) iterator.next();
220
221 // ensure only one double-dash is added
222 if (!"--".equals(str))
223 {
224 cmd.addArg(str);
225 }
226 }
227 }
228 }
229
230 processProperties(properties);
231 checkRequiredOptions();
232
233 return cmd;
234 }
235
236 /**
237 * Sets the values of Options using the values in <code>properties</code>.
238 *
239 * @param properties The value properties to be processed.
240 */
241 protected void processProperties(Properties properties)
242 {
243 if (properties == null)
244 {
245 return;
246 }
247
248 for (Enumeration e = properties.propertyNames(); e.hasMoreElements();)
249 {
250 String option = e.nextElement().toString();
251
252 if (!cmd.hasOption(option))
253 {
254 Option opt = getOptions().getOption(option);
255
256 // get the value from the properties instance
257 String value = properties.getProperty(option);
258
259 if (opt.hasArg())
260 {
261 if (opt.getValues() == null || opt.getValues().length == 0)
262 {
263 try
264 {
265 opt.addValueForProcessing(value);
266 }
267 catch (RuntimeException exp)
268 {
269 // if we cannot add the value don't worry about it
270 }
271 }
272 }
273 else if (!("yes".equalsIgnoreCase(value)
274 || "true".equalsIgnoreCase(value)
275 || "1".equalsIgnoreCase(value)))
276 {
277 // if the value is not yes, true or 1 then don't add the
278 // option to the CommandLine
279 break;
280 }
281
282 cmd.addOption(opt);
283 }
284 }
285 }
286
287 /**
288 * Throws a {@link MissingOptionException} if all of the required options
289 * are not present.
290 *
291 * @throws MissingOptionException if any of the required Options
292 * are not present.
293 */
294 protected void checkRequiredOptions() throws MissingOptionException
295 {
296 // if there are required options that have not been processsed
297 if (!getRequiredOptions().isEmpty())
298 {
299 throw new MissingOptionException(getRequiredOptions());
300 }
301 }
302
303 /**
304 * <p>Process the argument values for the specified Option
305 * <code>opt</code> using the values retrieved from the
306 * specified iterator <code>iter</code>.
307 *
308 * @param opt The current Option
309 * @param iter The iterator over the flattened command line
310 * Options.
311 *
312 * @throws ParseException if an argument value is required
313 * and it is has not been found.
314 */
315 public void processArgs(Option opt, ListIterator iter) throws ParseException
316 {
317 // loop until an option is found
318 while (iter.hasNext())
319 {
320 String str = (String) iter.next();
321
322 // found an Option, not an argument
323 if (getOptions().hasOption(str) && str.startsWith("-"))
324 {
325 iter.previous();
326 break;
327 }
328
329 // found a value
330 try
331 {
332 opt.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(str));
333 }
334 catch (RuntimeException exp)
335 {
336 iter.previous();
337 break;
338 }
339 }
340
341 if (opt.getValues() == null && !opt.hasOptionalArg())
342 {
343 throw new MissingArgumentException(opt);
344 }
345 }
346
347 /**
348 * Process the Option specified by <code>arg</code> using the values
349 * retrieved from the specfied iterator <code>iter</code>.
350 *
351 * @param arg The String value representing an Option
352 * @param iter The iterator over the flattened command line arguments.
353 *
354 * @throws ParseException if <code>arg</code> does not represent an Option
355 */
356 protected void processOption(String arg, ListIterator iter) throws ParseException
357 {
358 boolean hasOption = getOptions().hasOption(arg);
359
360 // if there is no option throw an UnrecognisedOptionException
361 if (!hasOption)
362 {
363 throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
364 }
365
366 // get the option represented by arg
367 Option opt = (Option) getOptions().getOption(arg).clone();
368
369 // if the option is a required option remove the option from
370 // the requiredOptions list
371 if (opt.isRequired())
372 {
373 getRequiredOptions().remove(opt.getKey());
374 }
375
376 // if the option is in an OptionGroup make that option the selected
377 // option of the group
378 if (getOptions().getOptionGroup(opt) != null)
379 {
380 OptionGroup group = getOptions().getOptionGroup(opt);
381
382 if (group.isRequired())
383 {
384 getRequiredOptions().remove(group);
385 }
386
387 group.setSelected(opt);
388 }
389
390 // if the option takes an argument value
391 if (opt.hasArg())
392 {
393 processArgs(opt, iter);
394 }
395
396 // set the option on the command line
397 cmd.addOption(opt);
398 }
399 }