View Javadoc
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.List;
24  import java.util.ListIterator;
25  import java.util.Properties;
26  
27  /**
28   * <code>Parser</code> creates {@link CommandLine}s.
29   *
30   * @deprecated since 1.3, the two-pass parsing with the flatten method is not enough flexible to handle complex cases
31   */
32  @Deprecated
33  public abstract class Parser implements CommandLineParser
34  {
35      /** commandline instance */
36      protected CommandLine cmd;
37  
38      /** current Options */
39      private Options options;
40  
41      /** list of required options strings */
42      private List requiredOptions;
43  
44      protected void setOptions(final Options options)
45      {
46          this.options = options;
47          this.requiredOptions = new ArrayList(options.getRequiredOptions());
48      }
49  
50      protected Options getOptions()
51      {
52          return options;
53      }
54  
55      protected List getRequiredOptions()
56      {
57          return requiredOptions;
58      }
59  
60      /**
61       * Subclasses must implement this method to reduce
62       * the <code>arguments</code> that have been passed to the parse method.
63       *
64       * @param opts The Options to parse the arguments by.
65       * @param arguments The arguments that have to be flattened.
66       * @param stopAtNonOption specifies whether to stop
67       * flattening when a non option has been encountered
68       * @return a String array of the flattened arguments
69       * @throws ParseException if there are any problems encountered
70       *                        while parsing the command line tokens.
71       */
72      protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption)
73              throws ParseException;
74  
75      /**
76       * Parses the specified <code>arguments</code> based
77       * on the specified {@link Options}.
78       *
79       * @param options the <code>Options</code>
80       * @param arguments the <code>arguments</code>
81       * @return the <code>CommandLine</code>
82       * @throws ParseException if there are any problems encountered
83       *                        while parsing the command line tokens.
84       */
85      public CommandLine parse(final Options options, final String[] arguments) throws ParseException
86      {
87          return parse(options, arguments, null, false);
88      }
89  
90      /**
91       * Parse the arguments according to the specified options and properties.
92       *
93       * @param options    the specified Options
94       * @param arguments  the command line arguments
95       * @param properties command line option name-value pairs
96       * @return the list of atomic option and value tokens
97       * @throws ParseException if there are any problems encountered
98       *                        while parsing the command line tokens.
99       *
100      * @since 1.1
101      */
102     public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException
103     {
104         return parse(options, arguments, properties, false);
105     }
106 
107     /**
108      * Parses the specified <code>arguments</code>
109      * based on the specified {@link Options}.
110      *
111      * @param options         the <code>Options</code>
112      * @param arguments       the <code>arguments</code>
113      * @param stopAtNonOption if <code>true</code> an unrecognized argument stops
114      *     the parsing and the remaining arguments are added to the 
115      *     {@link CommandLine}s args list. If <code>false</code> an unrecognized
116      *     argument triggers a ParseException.
117      * @return the <code>CommandLine</code>
118      * @throws ParseException if an error occurs when parsing the arguments.
119      */
120     public CommandLine parse(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException
121     {
122         return parse(options, arguments, null, stopAtNonOption);
123     }
124 
125     /**
126      * Parse the arguments according to the specified options and
127      * properties.
128      *
129      * @param options the specified Options
130      * @param arguments the command line arguments
131      * @param properties command line option name-value pairs
132      * @param stopAtNonOption if <code>true</code> an unrecognized argument stops
133      *     the parsing and the remaining arguments are added to the 
134      *     {@link CommandLine}s args list. If <code>false</code> an unrecognized
135      *     argument triggers a ParseException.
136      *
137      * @return the list of atomic option and value tokens
138      *
139      * @throws ParseException if there are any problems encountered
140      * while parsing the command line tokens.
141      *
142      * @since 1.1
143      */
144     public CommandLine parse(final Options options, String[] arguments, final Properties properties, final boolean stopAtNonOption)
145             throws ParseException
146     {
147         // clear out the data in options in case it's been used before (CLI-71)
148         for (final Option opt : options.helpOptions())
149         {
150             opt.clearValues();
151         }
152         
153         // clear the data from the groups
154         for (final OptionGroup group : options.getOptionGroups())
155         {
156             group.setSelected(null);
157         }        
158 
159         // initialise members
160         setOptions(options);
161 
162         cmd = new CommandLine();
163 
164         boolean eatTheRest = false;
165 
166         if (arguments == null)
167         {
168             arguments = new String[0];
169         }
170 
171         final List<String> tokenList = Arrays.asList(flatten(getOptions(), arguments, stopAtNonOption));
172 
173         final ListIterator<String> iterator = tokenList.listIterator();
174 
175         // process each flattened token
176         while (iterator.hasNext())
177         {
178             final String t = iterator.next();
179 
180             // the value is the double-dash
181             if ("--".equals(t))
182             {
183                 eatTheRest = true;
184             }
185 
186             // the value is a single dash
187             else if ("-".equals(t))
188             {
189                 if (stopAtNonOption)
190                 {
191                     eatTheRest = true;
192                 }
193                 else
194                 {
195                     cmd.addArg(t);
196                 }
197             }
198 
199             // the value is an option
200             else if (t.startsWith("-"))
201             {
202                 if (stopAtNonOption && !getOptions().hasOption(t))
203                 {
204                     eatTheRest = true;
205                     cmd.addArg(t);
206                 }
207                 else
208                 {
209                     processOption(t, iterator);
210                 }
211             }
212 
213             // the value is an argument
214             else
215             {
216                 cmd.addArg(t);
217 
218                 if (stopAtNonOption)
219                 {
220                     eatTheRest = true;
221                 }
222             }
223 
224             // eat the remaining tokens
225             if (eatTheRest)
226             {
227                 while (iterator.hasNext())
228                 {
229                     final String str = iterator.next();
230 
231                     // ensure only one double-dash is added
232                     if (!"--".equals(str))
233                     {
234                         cmd.addArg(str);
235                     }
236                 }
237             }
238         }
239 
240         processProperties(properties);
241         checkRequiredOptions();
242 
243         return cmd;
244     }
245 
246     /**
247      * Sets the values of Options using the values in <code>properties</code>.
248      *
249      * @param properties The value properties to be processed.
250      * @throws ParseException if there are any problems encountered
251      *                        while processing the properties.
252      */
253     protected void processProperties(final Properties properties) throws ParseException
254     {
255         if (properties == null)
256         {
257             return;
258         }
259 
260         for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();)
261         {
262             final String option = e.nextElement().toString();
263             
264             final Option opt = options.getOption(option);
265             if (opt == null)
266             {
267                 throw new UnrecognizedOptionException("Default option wasn't defined", option);
268             }
269             
270             // if the option is part of a group, check if another option of the group has been selected
271             final OptionGroup group = options.getOptionGroup(opt);
272             final boolean selected = group != null && group.getSelected() != null;
273             
274             if (!cmd.hasOption(option) && !selected)
275             {
276                 // get the value from the properties instance
277                 final String value = properties.getProperty(option);
278 
279                 if (opt.hasArg())
280                 {
281                     if (opt.getValues() == null || opt.getValues().length == 0)
282                     {
283                         try
284                         {
285                             opt.addValueForProcessing(value);
286                         }
287                         catch (final RuntimeException exp) //NOPMD
288                         {
289                             // if we cannot add the value don't worry about it
290                         }
291                     }
292                 }
293                 else if (!("yes".equalsIgnoreCase(value)
294                         || "true".equalsIgnoreCase(value)
295                         || "1".equalsIgnoreCase(value)))
296                 {
297                     // if the value is not yes, true or 1 then don't add the
298                     // option to the CommandLine
299                     continue;
300                 }
301 
302                 cmd.addOption(opt);
303                 updateRequiredOptions(opt);
304             }
305         }
306     }
307 
308     /**
309      * Throws a {@link MissingOptionException} if all of the required options
310      * are not present.
311      *
312      * @throws MissingOptionException if any of the required Options are not present.
313      */
314     protected void checkRequiredOptions() throws MissingOptionException
315     {
316         // if there are required options that have not been processed
317         if (!getRequiredOptions().isEmpty())
318         {
319             throw new MissingOptionException(getRequiredOptions());
320         }
321     }
322 
323     /**
324      * Process the argument values for the specified Option
325      * <code>opt</code> using the values retrieved from the
326      * specified iterator <code>iter</code>.
327      *
328      * @param opt The current Option
329      * @param iter The iterator over the flattened command line Options.
330      *
331      * @throws ParseException if an argument value is required
332      * and it is has not been found.
333      */
334     public void processArgs(final Option opt, final ListIterator<String> iter) throws ParseException
335     {
336         // loop until an option is found
337         while (iter.hasNext())
338         {
339             final String str = iter.next();
340             
341             // found an Option, not an argument
342             if (getOptions().hasOption(str) && str.startsWith("-"))
343             {
344                 iter.previous();
345                 break;
346             }
347 
348             // found a value
349             try
350             {
351                 opt.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(str));
352             }
353             catch (final RuntimeException exp)
354             {
355                 iter.previous();
356                 break;
357             }
358         }
359 
360         if (opt.getValues() == null && !opt.hasOptionalArg())
361         {
362             throw new MissingArgumentException(opt);
363         }
364     }
365 
366     /**
367      * Process the Option specified by <code>arg</code> using the values
368      * retrieved from the specified iterator <code>iter</code>.
369      *
370      * @param arg The String value representing an Option
371      * @param iter The iterator over the flattened command line arguments.
372      *
373      * @throws ParseException if <code>arg</code> does not represent an Option
374      */
375     protected void processOption(final String arg, final ListIterator<String> iter) throws ParseException
376     {
377         final boolean hasOption = getOptions().hasOption(arg);
378 
379         // if there is no option throw an UnrecognizedOptionException
380         if (!hasOption)
381         {
382             throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
383         }
384 
385         // get the option represented by arg
386         final Option opt = (Option) getOptions().getOption(arg).clone();
387         
388         // update the required options and groups
389         updateRequiredOptions(opt);
390         
391         // if the option takes an argument value
392         if (opt.hasArg())
393         {
394             processArgs(opt, iter);
395         }
396         
397         // set the option on the command line
398         cmd.addOption(opt);
399     }
400 
401     /**
402      * Removes the option or its group from the list of expected elements.
403      * 
404      * @param opt
405      */
406     private void updateRequiredOptions(final Option opt) throws ParseException
407     {
408         // if the option is a required option remove the option from
409         // the requiredOptions list
410         if (opt.isRequired())
411         {
412             getRequiredOptions().remove(opt.getKey());
413         }
414 
415         // if the option is in an OptionGroup make that option the selected
416         // option of the group
417         if (getOptions().getOptionGroup(opt) != null)
418         {
419             final OptionGroup group = getOptions().getOptionGroup(opt);
420 
421             if (group.isRequired())
422             {
423                 getRequiredOptions().remove(group);
424             }
425 
426             group.setSelected(opt);
427         }
428     }
429 }