001/* 002 Licensed to the Apache Software Foundation (ASF) under one or more 003 contributor license agreements. See the NOTICE file distributed with 004 this work for additional information regarding copyright ownership. 005 The ASF licenses this file to You under the Apache License, Version 2.0 006 (the "License"); you may not use this file except in compliance with 007 the License. You may obtain a copy of the License at 008 009 https://www.apache.org/licenses/LICENSE-2.0 010 011 Unless required by applicable law or agreed to in writing, software 012 distributed under the License is distributed on an "AS IS" BASIS, 013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 See the License for the specific language governing permissions and 015 limitations under the License. 016 */ 017 018package org.apache.commons.cli; 019 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Enumeration; 023import java.util.List; 024import java.util.ListIterator; 025import java.util.Properties; 026 027import org.apache.commons.cli.help.OptionFormatter; 028 029/** 030 * Creates {@link CommandLine} instances. 031 * 032 * @deprecated Since 1.3, the two-pass parsing with the flatten method is not enough flexible to handle complex cases. 033 */ 034@Deprecated 035public abstract class Parser implements CommandLineParser { 036 037 /** CommandLine instance */ 038 protected CommandLine cmd; 039 040 /** Current Options */ 041 private Options options; 042 043 /** List of required options strings */ 044 private List requiredOptions; 045 046 /** 047 * Constructs a new instance. 048 */ 049 public Parser() { 050 // empty 051 } 052 053 /** 054 * Throws a {@link MissingOptionException} if all of the required options are not present. 055 * 056 * @throws MissingOptionException if any of the required Options are not present. 057 */ 058 protected void checkRequiredOptions() throws MissingOptionException { 059 // if there are required options that have not been processed 060 if (!getRequiredOptions().isEmpty()) { 061 throw new MissingOptionException(getRequiredOptions()); 062 } 063 } 064 065 /** 066 * Subclasses must implement this method to reduce the {@code arguments} that have been passed to the parse method. 067 * 068 * @param opts The Options to parse the arguments by. 069 * @param arguments The arguments that have to be flattened. 070 * @param stopAtNonOption specifies whether to stop flattening when a non option has been encountered. 071 * @return a String array of the flattened arguments. 072 * @throws ParseException if there are any problems encountered while parsing the command line tokens. 073 */ 074 protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption) throws ParseException; 075 076 /** 077 * Gets the options. 078 * 079 * @return the options. 080 */ 081 protected Options getOptions() { 082 return options; 083 } 084 085 /** 086 * Gets the required options. 087 * 088 * @return the required options. 089 */ 090 protected List getRequiredOptions() { 091 return requiredOptions; 092 } 093 094 /** 095 * Parses the specified {@code arguments} based on the specified {@link Options}. 096 * 097 * @param options the {@code Options}. 098 * @param arguments the {@code arguments}. 099 * @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}