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