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