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