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