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