1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.cli;
19
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Enumeration;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.ListIterator;
26 import java.util.Properties;
27
28 /**
29 * <code>Parser</code> creates {@link CommandLine}s.
30 *
31 * @author John Keyes (john at integralsource.com)
32 * @version $Revision: 680644 $, $Date: 2008-07-29 09:13:48 +0100 (Tue, 29 Jul 2008) $
33 */
34 public abstract class Parser implements CommandLineParser
35 {
36 /** commandline instance */
37 protected CommandLine cmd;
38
39 /** current Options */
40 private Options options;
41
42 /** list of required options strings */
43 private List requiredOptions;
44
45 protected void setOptions(final Options options)
46 {
47 this.options = options;
48 this.requiredOptions = new ArrayList(options.getRequiredOptions());
49 }
50
51 protected Options getOptions()
52 {
53 return options;
54 }
55
56 protected List getRequiredOptions()
57 {
58 return requiredOptions;
59 }
60
61 /**
62 * Subclasses must implement this method to reduce
63 * the <code>arguments</code> that have been passed to the parse method.
64 *
65 * @param opts The Options to parse the arguments by.
66 * @param arguments The arguments that have to be flattened.
67 * @param stopAtNonOption specifies whether to stop
68 * flattening when a non option has been encountered
69 * @return a String array of the flattened arguments
70 */
71 protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption);
72
73 /**
74 * Parses the specified <code>arguments</code> based
75 * on the specifed {@link Options}.
76 *
77 * @param options the <code>Options</code>
78 * @param arguments the <code>arguments</code>
79 * @return the <code>CommandLine</code>
80 * @throws ParseException if an error occurs when parsing the
81 * arguments.
82 */
83 public CommandLine parse(Options options, String[] arguments) throws ParseException
84 {
85 return parse(options, arguments, null, false);
86 }
87
88 /**
89 * Parse the arguments according to the specified options and properties.
90 *
91 * @param options the specified Options
92 * @param arguments the command line arguments
93 * @param properties command line option name-value pairs
94 * @return the list of atomic option and value tokens
95 * @throws ParseException if there are any problems encountered
96 * while parsing the command line tokens.
97 *
98 * @since 1.1
99 */
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 }