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 */ 017package org.apache.commons.cli2.option; 018 019import java.util.Collections; 020import java.util.Comparator; 021import java.util.List; 022import java.util.ListIterator; 023import java.util.Set; 024import java.util.StringTokenizer; 025 026import org.apache.commons.cli2.Argument; 027import org.apache.commons.cli2.DisplaySetting; 028import org.apache.commons.cli2.HelpLine; 029import org.apache.commons.cli2.Option; 030import org.apache.commons.cli2.OptionException; 031import org.apache.commons.cli2.WriteableCommandLine; 032import org.apache.commons.cli2.resource.ResourceConstants; 033import org.apache.commons.cli2.resource.ResourceHelper; 034import org.apache.commons.cli2.validation.InvalidArgumentException; 035import org.apache.commons.cli2.validation.Validator; 036 037/** 038 * An implementation of an Argument. 039 */ 040public class ArgumentImpl 041 extends OptionImpl implements Argument { 042 private static final char NUL = '\0'; 043 044 /** 045 * The default value for the initial separator char. 046 */ 047 public static final char DEFAULT_INITIAL_SEPARATOR = NUL; 048 049 /** 050 * The default value for the subsequent separator char. 051 */ 052 public static final char DEFAULT_SUBSEQUENT_SEPARATOR = NUL; 053 054 /** 055 * The default token to indicate that remaining arguments should be consumed 056 * as values. 057 */ 058 public static final String DEFAULT_CONSUME_REMAINING = "--"; 059 private final String name; 060 private final String description; 061 private final int minimum; 062 private final int maximum; 063 private final char initialSeparator; 064 private final char subsequentSeparator; 065 private final boolean subsequentSplit; 066 private final Validator validator; 067 private final String consumeRemaining; 068 private final List defaultValues; 069 private final ResourceHelper resources = ResourceHelper.getResourceHelper(); 070 071 /** 072 * Creates a new Argument instance. 073 * 074 * @param name 075 * The name of the argument 076 * @param description 077 * A description of the argument 078 * @param minimum 079 * The minimum number of values needed to be valid 080 * @param maximum 081 * The maximum number of values allowed to be valid 082 * @param initialSeparator 083 * The char separating option from value 084 * @param subsequentSeparator 085 * The char separating values from each other 086 * @param validator 087 * The object responsible for validating the values 088 * @param consumeRemaining 089 * The String used for the "consuming option" group 090 * @param valueDefaults 091 * The values to be used if none are specified. 092 * @param id 093 * The id of the option, 0 implies automatic assignment. 094 * 095 * @see OptionImpl#OptionImpl(int,boolean) 096 */ 097 public ArgumentImpl(final String name, 098 final String description, 099 final int minimum, 100 final int maximum, 101 final char initialSeparator, 102 final char subsequentSeparator, 103 final Validator validator, 104 final String consumeRemaining, 105 final List valueDefaults, 106 final int id) { 107 super(id, false); 108 109 this.name = (name == null) ? "arg" : name; 110 this.description = description; 111 this.minimum = minimum; 112 this.maximum = maximum; 113 this.initialSeparator = initialSeparator; 114 this.subsequentSeparator = subsequentSeparator; 115 this.subsequentSplit = subsequentSeparator != NUL; 116 this.validator = validator; 117 this.consumeRemaining = consumeRemaining; 118 this.defaultValues = valueDefaults; 119 120 if (minimum > maximum) { 121 throw new IllegalArgumentException(resources.getMessage(ResourceConstants.ARGUMENT_MIN_EXCEEDS_MAX)); 122 } 123 124 if ((valueDefaults != null) && (valueDefaults.size() > 0)) { 125 if (valueDefaults.size() < minimum) { 126 throw new IllegalArgumentException(resources.getMessage(ResourceConstants.ARGUMENT_TOO_FEW_DEFAULTS)); 127 } 128 129 if (valueDefaults.size() > maximum) { 130 throw new IllegalArgumentException(resources.getMessage(ResourceConstants.ARGUMENT_TOO_MANY_DEFAULTS)); 131 } 132 } 133 } 134 135 public String getPreferredName() { 136 return name; 137 } 138 139 public void processValues(final WriteableCommandLine commandLine, 140 final ListIterator arguments, 141 final Option option) 142 throws OptionException { 143 // count of arguments processed for this option. 144 int argumentCount = commandLine.getUndefaultedValues(option).size(); 145 146 while (arguments.hasNext() && (argumentCount < maximum)) { 147 final String allValuesQuoted = (String) arguments.next(); 148 final String allValues = stripBoundaryQuotes(allValuesQuoted); 149 150 // should we ignore things that look like options? 151 if (allValuesQuoted.equals(consumeRemaining)) { 152 while (arguments.hasNext() && (argumentCount < maximum)) { 153 ++argumentCount; 154 commandLine.addValue(option, arguments.next()); 155 } 156 } 157 // does it look like an option? 158 else if (commandLine.looksLikeOption(allValuesQuoted)) { 159 arguments.previous(); 160 161 break; 162 } 163 // should we split the string up? 164 else if (subsequentSplit) { 165 final StringTokenizer values = 166 new StringTokenizer(allValues, String.valueOf(subsequentSeparator)); 167 168 arguments.remove(); 169 170 while (values.hasMoreTokens() && (argumentCount < maximum)) { 171 ++argumentCount; 172 173 final String token = values.nextToken(); 174 commandLine.addValue(option, token); 175 arguments.add(token); 176 } 177 178 if (values.hasMoreTokens()) { 179 throw new OptionException(option, ResourceConstants.ARGUMENT_UNEXPECTED_VALUE, 180 values.nextToken()); 181 } 182 } 183 // it must be a value as it is 184 else { 185 ++argumentCount; 186 commandLine.addValue(option, allValues); 187 } 188 } 189 } 190 191 public boolean canProcess(final WriteableCommandLine commandLine, 192 final String arg) { 193 return true; 194 } 195 196 public Set getPrefixes() { 197 return Collections.EMPTY_SET; 198 } 199 200 public void process(WriteableCommandLine commandLine, 201 ListIterator args) 202 throws OptionException { 203 processValues(commandLine, args, this); 204 } 205 206 public char getInitialSeparator() { 207 return this.initialSeparator; 208 } 209 210 public char getSubsequentSeparator() { 211 return this.subsequentSeparator; 212 } 213 214 public Set getTriggers() { 215 return Collections.EMPTY_SET; 216 } 217 218 public String getConsumeRemaining() { 219 return this.consumeRemaining; 220 } 221 222 public List getDefaultValues() { 223 return this.defaultValues; 224 } 225 226 public Validator getValidator() { 227 return this.validator; 228 } 229 230 public void validate(final WriteableCommandLine commandLine) 231 throws OptionException { 232 validate(commandLine, this); 233 } 234 235 public void validate(final WriteableCommandLine commandLine, 236 final Option option) 237 throws OptionException { 238 final List values = commandLine.getValues(option); 239 240 if (values.size() < minimum) { 241 throw new OptionException(option, ResourceConstants.ARGUMENT_MISSING_VALUES); 242 } 243 244 if (values.size() > maximum) { 245 throw new OptionException(option, ResourceConstants.ARGUMENT_UNEXPECTED_VALUE, 246 (String) values.get(maximum)); 247 } 248 249 if (validator != null) { 250 try { 251 validator.validate(values); 252 } catch (InvalidArgumentException ive) { 253 throw new OptionException(option, ResourceConstants.ARGUMENT_UNEXPECTED_VALUE, 254 ive.getMessage()); 255 } 256 } 257 } 258 259 public void appendUsage(final StringBuffer buffer, 260 final Set helpSettings, 261 final Comparator comp) { 262 // do we display the outer optionality 263 final boolean optional = helpSettings.contains(DisplaySetting.DISPLAY_OPTIONAL); 264 265 // allow numbering if multiple args 266 final boolean numbered = 267 (maximum > 1) && helpSettings.contains(DisplaySetting.DISPLAY_ARGUMENT_NUMBERED); 268 269 final boolean bracketed = helpSettings.contains(DisplaySetting.DISPLAY_ARGUMENT_BRACKETED); 270 271 // if infinite args are allowed then crop the list 272 final int max = (maximum == Integer.MAX_VALUE) ? 2 : maximum; 273 274 int i = 0; 275 276 // for each argument 277 while (i < max) { 278 // if we're past the first add a space 279 if (i > 0) { 280 buffer.append(' '); 281 } 282 283 // if the next arg is optional 284 if ((i >= minimum) && (optional || (i > 0))) { 285 buffer.append('['); 286 } 287 288 if (bracketed) { 289 buffer.append('<'); 290 } 291 292 // add name 293 buffer.append(name); 294 ++i; 295 296 // if numbering 297 if (numbered) { 298 buffer.append(i); 299 } 300 301 if (bracketed) { 302 buffer.append('>'); 303 } 304 } 305 306 // if infinite args are allowed 307 if (maximum == Integer.MAX_VALUE) { 308 // append elipsis 309 buffer.append(" ..."); 310 } 311 312 // for each argument 313 while (i > 0) { 314 --i; 315 316 // if the next arg is optional 317 if ((i >= minimum) && (optional || (i > 0))) { 318 buffer.append(']'); 319 } 320 } 321 } 322 323 public String getDescription() { 324 return description; 325 } 326 327 public List helpLines(final int depth, 328 final Set helpSettings, 329 final Comparator comp) { 330 final HelpLine helpLine = new HelpLineImpl(this, depth); 331 332 return Collections.singletonList(helpLine); 333 } 334 335 public int getMaximum() { 336 return maximum; 337 } 338 339 public int getMinimum() { 340 return minimum; 341 } 342 343 /** 344 * If there are any leading or trailing quotes remove them from the 345 * specified token. 346 * 347 * @param token 348 * the token to strip leading and trailing quotes 349 * 350 * @return String the possibly modified token 351 */ 352 public String stripBoundaryQuotes(String token) { 353 if (!token.startsWith("\"") || !token.endsWith("\"")) { 354 return token; 355 } 356 357 token = token.substring(1, token.length() - 1); 358 359 return token; 360 } 361 362 public boolean isRequired() { 363 return getMinimum() > 0; 364 } 365 366 public void defaults(final WriteableCommandLine commandLine) { 367 super.defaults(commandLine); 368 defaultValues(commandLine, this); 369 } 370 371 public void defaultValues(final WriteableCommandLine commandLine, 372 final Option option) { 373 commandLine.setDefaultValues(option, defaultValues); 374 } 375}