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 http://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 027/** 028 * {@code Parser} creates {@link CommandLine}s. 029 * 030 * @deprecated since 1.3, the two-pass parsing with the flatten method is not enough flexible to handle complex cases 031 */ 032@Deprecated 033public abstract class Parser implements CommandLineParser { 034 /** CommandLine instance */ 035 protected CommandLine cmd; 036 037 /** current Options */ 038 private Options options; 039 040 /** list of required options strings */ 041 private List requiredOptions; 042 043 /** 044 * Throws a {@link MissingOptionException} if all of the required options are not present. 045 * 046 * @throws MissingOptionException if any of the required Options are not present. 047 */ 048 protected void checkRequiredOptions() throws MissingOptionException { 049 // if there are required options that have not been processed 050 if (!getRequiredOptions().isEmpty()) { 051 throw new MissingOptionException(getRequiredOptions()); 052 } 053 } 054 055 /** 056 * Subclasses must implement this method to reduce the {@code arguments} that have been passed to the parse method. 057 * 058 * @param opts The Options to parse the arguments by. 059 * @param arguments The arguments that have to be flattened. 060 * @param stopAtNonOption specifies whether to stop flattening when a non option has been encountered 061 * @return a String array of the flattened arguments 062 * @throws ParseException if there are any problems encountered while parsing the command line tokens. 063 */ 064 protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption) throws ParseException; 065 066 /** 067 * Gets the options. 068 * 069 * @return the options. 070 */ 071 protected Options getOptions() { 072 return options; 073 } 074 075 /** 076 * Gets the required options. 077 * 078 * @return the required options. 079 */ 080 protected List getRequiredOptions() { 081 return requiredOptions; 082 } 083 084 /** 085 * Parses the specified {@code arguments} based on the specified {@link Options}. 086 * 087 * @param options the {@code Options} 088 * @param arguments the {@code arguments} 089 * @return the {@code CommandLine} 090 * @throws ParseException if there are any problems encountered while parsing the command line tokens. 091 */ 092 @Override 093 public CommandLine parse(final Options options, final String[] arguments) throws ParseException { 094 return parse(options, arguments, null, false); 095 } 096 097 /** 098 * Parses the specified {@code arguments} based on the specified {@link Options}. 099 * 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 * 122 * @since 1.1 123 */ 124 public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException { 125 return parse(options, arguments, properties, false); 126 } 127 128 /** 129 * Parse the arguments according to the specified options and properties. 130 * 131 * @param options the specified Options 132 * @param arguments the command line arguments 133 * @param properties command line option name-value pairs 134 * @param stopAtNonOption if {@code true} an unrecognized argument stops the parsing and the remaining arguments 135 * are added to the {@link CommandLine}s args list. If {@code false} an unrecognized argument triggers a 136 * ParseException. 137 * 138 * @return the list of atomic option and value tokens 139 * 140 * @throws ParseException if there are any problems encountered while parsing the command line tokens. 141 * 142 * @since 1.1 143 */ 144 public CommandLine parse(final Options options, String[] arguments, final Properties properties, final boolean stopAtNonOption) throws ParseException { 145 // clear out the data in options in case it's been used before (CLI-71) 146 for (final Option opt : options.helpOptions()) { 147 opt.clearValues(); 148 } 149 150 // clear the data from the groups 151 for (final OptionGroup group : options.getOptionGroups()) { 152 group.setSelected(null); 153 } 154 155 // initialize members 156 setOptions(options); 157 158 cmd = new CommandLine(); 159 160 boolean eatTheRest = false; 161 162 if (arguments == null) { 163 arguments = new String[0]; 164 } 165 166 final List<String> tokenList = Arrays.asList(flatten(getOptions(), arguments, stopAtNonOption)); 167 168 final ListIterator<String> iterator = tokenList.listIterator(); 169 170 // process each flattened token 171 while (iterator.hasNext()) { 172 final String t = iterator.next(); 173 174 // the value is the double-dash 175 if ("--".equals(t)) { 176 eatTheRest = true; 177 } 178 179 // the value is a single dash 180 else if ("-".equals(t)) { 181 if (stopAtNonOption) { 182 eatTheRest = true; 183 } else { 184 cmd.addArg(t); 185 } 186 } 187 188 // the value is an option 189 else if (t.startsWith("-")) { 190 if (stopAtNonOption && !getOptions().hasOption(t)) { 191 eatTheRest = true; 192 cmd.addArg(t); 193 } else { 194 processOption(t, iterator); 195 } 196 } 197 198 // the value is an argument 199 else { 200 cmd.addArg(t); 201 202 if (stopAtNonOption) { 203 eatTheRest = true; 204 } 205 } 206 207 // eat the remaining tokens 208 if (eatTheRest) { 209 while (iterator.hasNext()) { 210 final String str = iterator.next(); 211 212 // ensure only one double-dash is added 213 if (!"--".equals(str)) { 214 cmd.addArg(str); 215 } 216 } 217 } 218 } 219 220 processProperties(properties); 221 checkRequiredOptions(); 222 223 return cmd; 224 } 225 226 /** 227 * Process the argument values for the specified Option {@code opt} using the values retrieved from the specified 228 * iterator {@code iter}. 229 * 230 * @param opt The current Option 231 * @param iter The iterator over the flattened command line Options. 232 * 233 * @throws ParseException if an argument value is required and it is has not been found. 234 */ 235 public void processArgs(final Option opt, final ListIterator<String> iter) throws ParseException { 236 // loop until an option is found 237 while (iter.hasNext()) { 238 final String str = iter.next(); 239 240 // found an Option, not an argument 241 if (getOptions().hasOption(str) && str.startsWith("-")) { 242 iter.previous(); 243 break; 244 } 245 246 // found a value 247 try { 248 opt.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(str)); 249 } catch (final RuntimeException exp) { 250 iter.previous(); 251 break; 252 } 253 } 254 255 if (opt.getValues() == null && !opt.hasOptionalArg()) { 256 throw new MissingArgumentException(opt); 257 } 258 } 259 260 /** 261 * Process the Option specified by {@code arg} using the values retrieved from the specified iterator 262 * {@code iter}. 263 * 264 * @param arg The String value representing an Option 265 * @param iter The iterator over the flattened command line arguments. 266 * 267 * @throws ParseException if {@code arg} does not represent an Option 268 */ 269 protected void processOption(final String arg, final ListIterator<String> iter) throws ParseException { 270 final boolean hasOption = getOptions().hasOption(arg); 271 272 // if there is no option throw an UnrecognizedOptionException 273 if (!hasOption) { 274 throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg); 275 } 276 277 // get the option represented by arg 278 final Option opt = (Option) getOptions().getOption(arg).clone(); 279 280 // update the required options and groups 281 updateRequiredOptions(opt); 282 283 // if the option takes an argument value 284 if (opt.hasArg()) { 285 processArgs(opt, iter); 286 } 287 288 // set the option on the command line 289 cmd.addOption(opt); 290 } 291 292 /** 293 * Sets the values of Options using the values in {@code properties}. 294 * 295 * @param properties The value properties to be processed. 296 * @throws ParseException if there are any problems encountered while processing the properties. 297 */ 298 protected void processProperties(final Properties properties) throws ParseException { 299 if (properties == null) { 300 return; 301 } 302 303 for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) { 304 final String option = e.nextElement().toString(); 305 306 final Option opt = options.getOption(option); 307 if (opt == null) { 308 throw new UnrecognizedOptionException("Default option wasn't defined", option); 309 } 310 311 // if the option is part of a group, check if another option of the group has been selected 312 final OptionGroup group = options.getOptionGroup(opt); 313 final boolean selected = group != null && group.getSelected() != null; 314 315 if (!cmd.hasOption(option) && !selected) { 316 // get the value from the properties instance 317 final String value = properties.getProperty(option); 318 319 if (opt.hasArg()) { 320 if (opt.getValues() == null || opt.getValues().length == 0) { 321 try { 322 opt.addValueForProcessing(value); 323 } catch (final RuntimeException exp) { // NOPMD 324 // if we cannot add the value don't worry about it 325 } 326 } 327 } else if (!("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value))) { 328 // if the value is not yes, true or 1 then don't add the 329 // option to the CommandLine 330 continue; 331 } 332 333 cmd.addOption(opt); 334 updateRequiredOptions(opt); 335 } 336 } 337 } 338 339 /** 340 * Sets the options. 341 * 342 * @param options the options. 343 */ 344 protected void setOptions(final Options options) { 345 this.options = options; 346 this.requiredOptions = new ArrayList<>(options.getRequiredOptions()); 347 } 348 349 /** 350 * Removes the option or its group from the list of expected elements. 351 * 352 * @param opt 353 */ 354 private void updateRequiredOptions(final Option opt) throws ParseException { 355 // if the option is a required option remove the option from 356 // the requiredOptions list 357 if (opt.isRequired()) { 358 getRequiredOptions().remove(opt.getKey()); 359 } 360 361 // if the option is in an OptionGroup make that option the selected 362 // option of the group 363 if (getOptions().getOptionGroup(opt) != null) { 364 final OptionGroup group = getOptions().getOptionGroup(opt); 365 366 if (group.isRequired()) { 367 getRequiredOptions().remove(group); 368 } 369 370 group.setSelected(opt); 371 } 372 } 373}