Frames | No Frames |
1: /* 2: * Copyright 2003-2005 The Apache Software Foundation 3: * Copyright 2005 Stephen McConnell 4: * 5: * Licensed under the Apache License, Version 2.0 (the "License"); 6: * you may not use this file except in compliance with the License. 7: * You may obtain a copy of the License at 8: * 9: * http://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: package net.dpml.cli.option; 18: 19: import java.util.Collections; 20: import java.util.Comparator; 21: import java.util.List; 22: import java.util.ListIterator; 23: import java.util.Set; 24: import java.util.StringTokenizer; 25: 26: import net.dpml.cli.Argument; 27: import net.dpml.cli.DisplaySetting; 28: import net.dpml.cli.HelpLine; 29: import net.dpml.cli.Option; 30: import net.dpml.cli.OptionException; 31: import net.dpml.cli.WriteableCommandLine; 32: import net.dpml.cli.resource.ResourceConstants; 33: import net.dpml.cli.resource.ResourceHelper; 34: import net.dpml.cli.validation.InvalidArgumentException; 35: import net.dpml.cli.validation.Validator; 36: 37: /** 38: * An implementation of an Argument. 39: * @author <a href="@PUBLISHER-URL@">@PUBLISHER-NAME@</a> 40: * @version @PROJECT-VERSION@ 41: */ 42: public class ArgumentImpl extends OptionImpl implements Argument 43: { 44: private static final char NUL = '\0'; 45: 46: /** 47: * The default value for the initial separator char. 48: */ 49: public static final char DEFAULT_INITIAL_SEPARATOR = NUL; 50: 51: /** 52: * The default value for the subsequent separator char. 53: */ 54: public static final char DEFAULT_SUBSEQUENT_SEPARATOR = NUL; 55: 56: /** 57: * The default token to indicate that remaining arguments should be consumed 58: * as values. 59: */ 60: public static final String DEFAULT_CONSUME_REMAINING = "--"; 61: 62: private final String m_name; 63: private final String m_description; 64: private final int m_minimum; 65: private final int m_maximum; 66: private final char m_initialSeparator; 67: private final char m_subsequentSeparator; 68: private final boolean m_subsequentSplit; 69: private final Validator m_validator; 70: private final String m_consumeRemaining; 71: private final List m_defaultValues; 72: private final ResourceHelper m_resources = ResourceHelper.getResourceHelper(); 73: 74: /** 75: * Creates a new Argument instance. 76: * 77: * @param name the name of the argument 78: * @param description a description of the argument 79: * @param minimum the minimum number of values needed to be valid 80: * @param maximum the maximum number of values allowed to be valid 81: * @param initialSeparator the char separating option from value 82: * @param subsequentSeparator the char separating values from each other 83: * @param validator object responsible for validating the values 84: * @param consumeRemaining String used for the "consuming option" group 85: * @param valueDefaults values to be used if none are specified. 86: * @param id the id of the option, 0 implies automatic assignment. 87: * 88: * @see OptionImpl#OptionImpl(int,boolean) 89: */ 90: public ArgumentImpl( 91: final String name, final String description, final int minimum, final int maximum, 92: final char initialSeparator, final char subsequentSeparator, final Validator validator, 93: final String consumeRemaining, final List valueDefaults, final int id ) 94: { 95: super( id, false ); 96: 97: m_description = description; 98: m_minimum = minimum; 99: m_maximum = maximum; 100: m_initialSeparator = initialSeparator; 101: m_subsequentSeparator = subsequentSeparator; 102: m_subsequentSplit = subsequentSeparator != NUL; 103: m_validator = validator; 104: m_consumeRemaining = consumeRemaining; 105: m_defaultValues = valueDefaults; 106: 107: if( null == name ) 108: { 109: m_name = "arg"; 110: } 111: else 112: { 113: m_name = name; 114: } 115: 116: if( m_minimum > m_maximum ) 117: { 118: throw new IllegalArgumentException( 119: m_resources.getMessage( 120: ResourceConstants.ARGUMENT_MIN_EXCEEDS_MAX ) ); 121: } 122: 123: if( ( m_defaultValues != null ) && ( m_defaultValues.size() > 0 ) ) 124: { 125: if( valueDefaults.size() < minimum ) 126: { 127: throw new IllegalArgumentException( 128: m_resources.getMessage( 129: ResourceConstants.ARGUMENT_TOO_FEW_DEFAULTS ) ); 130: } 131: if( m_defaultValues.size() > maximum ) 132: { 133: throw new IllegalArgumentException( 134: m_resources.getMessage( 135: ResourceConstants.ARGUMENT_TOO_MANY_DEFAULTS ) ); 136: } 137: } 138: } 139: 140: /** 141: * The preferred name of an option is used for generating help and usage 142: * information. 143: * 144: * @return The preferred name of the option 145: */ 146: public String getPreferredName() 147: { 148: return m_name; 149: } 150: 151: /** 152: * Processes the "README" style element of the argument. 153: * 154: * Values identified should be added to the CommandLine object in 155: * association with this Argument. 156: * 157: * @see WriteableCommandLine#addValue(Option,Object) 158: * 159: * @param commandLine The CommandLine object to store results in. 160: * @param arguments The arguments to process. 161: * @param option The option to register value against. 162: * @throws OptionException if any problems occur. 163: */ 164: public void processValues( 165: final WriteableCommandLine commandLine, final ListIterator arguments, final Option option ) 166: throws OptionException 167: { 168: int argumentCount = commandLine.getValues( option, Collections.EMPTY_LIST ).size(); 169: 170: while( arguments.hasNext() && ( argumentCount < m_maximum ) ) 171: { 172: final String allValues = stripBoundaryQuotes( (String) arguments.next() ); 173: 174: // should we ignore things that look like options? 175: if( allValues.equals( m_consumeRemaining ) ) 176: { 177: while( arguments.hasNext() && ( argumentCount < m_maximum ) ) 178: { 179: ++argumentCount; 180: commandLine.addValue( option, arguments.next() ); 181: } 182: } 183: // does it look like an option? 184: else if( commandLine.looksLikeOption( allValues ) ) 185: { 186: arguments.previous(); 187: break; 188: } 189: // should we split the string up? 190: else if( m_subsequentSplit ) 191: { 192: final StringTokenizer values = 193: new StringTokenizer( allValues, String.valueOf( m_subsequentSeparator ) ); 194: arguments.remove(); 195: 196: while( values.hasMoreTokens() && ( argumentCount < m_maximum ) ) 197: { 198: ++argumentCount; 199: final String token = values.nextToken(); 200: commandLine.addValue( option, token ); 201: arguments.add( token ); 202: } 203: 204: if( values.hasMoreTokens() ) 205: { 206: throw new OptionException( 207: option, 208: ResourceConstants.ARGUMENT_UNEXPECTED_VALUE, 209: values.nextToken() ); 210: } 211: } 212: else 213: { 214: // it must be a value as it is 215: ++argumentCount; 216: commandLine.addValue( option, allValues ); 217: } 218: } 219: } 220: 221: /** 222: * Indicates whether this Option will be able to process the particular 223: * argument. 224: * 225: * @param commandLine the CommandLine object to store defaults in 226: * @param argument the argument to be tested 227: * @return true if the argument can be processed by this Option 228: */ 229: public boolean canProcess( final WriteableCommandLine commandLine, final String argument ) 230: { 231: return true; 232: } 233: 234: /** 235: * Identifies the argument prefixes that should be considered options. This 236: * is used to identify whether a given string looks like an option or an 237: * argument value. Typically an option would return the set [--,-] while 238: * switches might offer [-,+]. 239: * 240: * The returned Set must not be null. 241: * 242: * @return The set of prefixes for this Option 243: */ 244: public Set getPrefixes() 245: { 246: return Collections.EMPTY_SET; 247: } 248: 249: /** 250: * Processes String arguments into a CommandLine. 251: * 252: * The iterator will initially point at the first argument to be processed 253: * and at the end of the method should point to the first argument not 254: * processed. This method MUST process at least one argument from the 255: * ListIterator. 256: * 257: * @param commandLine the CommandLine object to store results in 258: * @param args the arguments to process 259: * @throws OptionException if any problems occur 260: */ 261: public void process( WriteableCommandLine commandLine, ListIterator args ) 262: throws OptionException 263: { 264: processValues( commandLine, args, this ); 265: } 266: 267: /** 268: * Returns the initial separator character or 269: * '\0' if no character has been set. 270: * 271: * @return char the initial separator character 272: */ 273: public char getInitialSeparator() 274: { 275: return m_initialSeparator; 276: } 277: 278: /** 279: * Returns the subsequent separator character. 280: * 281: * @return the subsequent separator character 282: */ 283: public char getSubsequentSeparator() 284: { 285: return m_subsequentSeparator; 286: } 287: 288: /** 289: * Identifies the argument prefixes that should trigger this option. This 290: * is used to decide which of many Options should be tried when processing 291: * a given argument string. 292: * 293: * The returned Set must not be null. 294: * 295: * @return The set of triggers for this Option 296: */ 297: public Set getTriggers() 298: { 299: return Collections.EMPTY_SET; 300: } 301: 302: /** 303: * Return the consume remaining flag. 304: * @return the consume remaining flag 305: */ 306: public String getConsumeRemaining() 307: { 308: return m_consumeRemaining; 309: } 310: 311: /** 312: * Return the list of default values. 313: * @return the default values 314: */ 315: public List getDefaultValues() 316: { 317: return m_defaultValues; 318: } 319: 320: /** 321: * Return the argument validator. 322: * @return the validator 323: */ 324: public Validator getValidator() 325: { 326: return m_validator; 327: } 328: 329: /** 330: * Performs any necessary validation on the values added to the 331: * CommandLine. 332: * 333: * Validation will typically involve using the 334: * CommandLine.getValues(option) method to retrieve the values 335: * and then either checking each value. Optionally the String 336: * value can be replaced by another Object such as a Number 337: * instance or a File instance. 338: * 339: * @see net.dpml.cli.CommandLine#getValues(Option) 340: * 341: * @param commandLine The CommandLine object to query. 342: * @throws OptionException if any problems occur. 343: */ 344: public void validate( final WriteableCommandLine commandLine ) throws OptionException 345: { 346: validate( commandLine, this ); 347: } 348: 349: /** 350: * Performs any necessary validation on the values added to the 351: * CommandLine. 352: * 353: * Validation will typically involve using the 354: * CommandLine.getValues(option) method to retrieve the values 355: * and then either checking each value. Optionally the String 356: * value can be replaced by another Object such as a Number 357: * instance or a File instance. 358: * 359: * @see net.dpml.cli.CommandLine#getValues(Option) 360: * 361: * @param commandLine The CommandLine object to query. 362: * @param option The option to lookup values with. 363: * @throws OptionException if any problems occur. 364: */ 365: public void validate( 366: final WriteableCommandLine commandLine, final Option option ) 367: throws OptionException 368: { 369: final List values = commandLine.getValues( option ); 370: if( values.size() < m_minimum ) 371: { 372: throw new OptionException( 373: option, 374: ResourceConstants.ARGUMENT_MISSING_VALUES ); 375: } 376: 377: if( values.size() > m_maximum ) 378: { 379: throw new OptionException( 380: option, 381: ResourceConstants.ARGUMENT_UNEXPECTED_VALUE, 382: (String) values.get( m_maximum ) ); 383: } 384: 385: if( m_validator != null ) 386: { 387: try 388: { 389: m_validator.validate( values ); 390: } 391: catch( InvalidArgumentException ive ) 392: { 393: throw new OptionException( 394: option, 395: ResourceConstants.ARGUMENT_UNEXPECTED_VALUE, 396: ive.getMessage() ); 397: } 398: } 399: } 400: 401: /** 402: * Appends usage information to the specified StringBuffer 403: * 404: * @param buffer the buffer to append to 405: * @param helpSettings a set of display settings @see DisplaySetting 406: * @param comp a comparator used to sort the Options 407: */ 408: public void appendUsage( 409: final StringBuffer buffer, final Set helpSettings, final Comparator comp ) 410: { 411: // do we display the outer optionality 412: final boolean optional = helpSettings.contains( DisplaySetting.DISPLAY_OPTIONAL ); 413: 414: // allow numbering if multiple args 415: final boolean numbered = 416: ( m_maximum > 1 ) 417: && helpSettings.contains( DisplaySetting.DISPLAY_ARGUMENT_NUMBERED ); 418: 419: final boolean bracketed = helpSettings.contains( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 420: 421: // if infinite args are allowed then crop the list 422: final int max = getMaxValue(); 423: 424: int i = 0; 425: 426: // for each argument 427: while( i < max ) 428: { 429: // if we're past the first add a space 430: if( i > 0 ) 431: { 432: buffer.append( ' ' ); 433: } 434: 435: // if the next arg is optional 436: if( ( i >= m_minimum ) && ( optional || ( i > 0 ) ) ) 437: { 438: buffer.append( '[' ); 439: } 440: 441: if( bracketed ) 442: { 443: buffer.append( '<' ); 444: } 445: 446: // add name 447: buffer.append( m_name ); 448: ++i; 449: 450: // if numbering 451: if( numbered ) 452: { 453: buffer.append( i ); 454: } 455: 456: if( bracketed ) 457: { 458: buffer.append( '>' ); 459: } 460: } 461: 462: // if infinite args are allowed 463: if( m_maximum == Integer.MAX_VALUE ) 464: { 465: // append elipsis 466: buffer.append( " ..." ); 467: } 468: 469: // for each argument 470: while( i > 0 ) 471: { 472: --i; 473: // if the next arg is optional 474: if( ( i >= m_minimum ) && ( optional || ( i > 0 ) ) ) 475: { 476: buffer.append( ']' ); 477: } 478: } 479: } 480: 481: /** 482: * Returns a description of the option. This string is used to build help 483: * messages as in the HelpFormatter. 484: * 485: * @see net.dpml.cli.util.HelpFormatter 486: * @return a description of the option. 487: */ 488: public String getDescription() 489: { 490: return m_description; 491: } 492: 493: /** 494: * Builds up a list of HelpLineImpl instances to be presented by HelpFormatter. 495: * 496: * @see HelpLine 497: * @see net.dpml.cli.util.HelpFormatter 498: * @param depth the initial indent depth 499: * @param helpSettings the HelpSettings that should be applied 500: * @param comp a comparator used to sort options when applicable. 501: * @return a List of HelpLineImpl objects 502: */ 503: public List helpLines( final int depth, final Set helpSettings, final Comparator comp ) 504: { 505: final HelpLine helpLine = new HelpLineImpl( this, depth ); 506: return Collections.singletonList( helpLine ); 507: } 508: 509: /** 510: * Retrieves the maximum number of values acceptable for a valid Argument 511: * 512: * @return the maximum number of values 513: */ 514: public int getMaximum() 515: { 516: return m_maximum; 517: } 518: 519: /** 520: * Retrieves the minimum number of values required for a valid Argument 521: * 522: * @return the minimum number of values 523: */ 524: public int getMinimum() 525: { 526: return m_minimum; 527: } 528: 529: /** 530: * If there are any leading or trailing quotes remove them from the 531: * specified token. 532: * 533: * @param token the token to strip leading and trailing quotes 534: * @return String the possibly modified token 535: */ 536: public String stripBoundaryQuotes( String token ) 537: { 538: if( !token.startsWith( "\"" ) || !token.endsWith( "\"" ) ) 539: { 540: return token; 541: } 542: token = token.substring( 1, token.length() - 1 ); 543: return token; 544: } 545: 546: /** 547: * Indicates whether argument values must be present for the CommandLine to 548: * be valid. 549: * 550: * @see #getMinimum() 551: * @see #getMaximum() 552: * @return true iff the CommandLine will be invalid without at least one 553: * value 554: */ 555: public boolean isRequired() 556: { 557: return getMinimum() > 0; 558: } 559: 560: /** 561: * Adds defaults to a CommandLine. 562: * 563: * @param commandLine the CommandLine object to store defaults in. 564: */ 565: public void defaults( final WriteableCommandLine commandLine ) 566: { 567: super.defaults( commandLine ); 568: defaultValues( commandLine, this ); 569: } 570: 571: /** 572: * Adds defaults to a CommandLine. 573: * 574: * @param commandLine the CommandLine object to store defaults in. 575: * @param option the Option to store the defaults against. 576: */ 577: public void defaultValues( final WriteableCommandLine commandLine, final Option option ) 578: { 579: commandLine.setDefaultValues( option, m_defaultValues ); 580: } 581: 582: private int getMaxValue() 583: { 584: if( m_maximum == Integer.MAX_VALUE ) 585: { 586: return 2; 587: } 588: else 589: { 590: return m_maximum; 591: } 592: } 593: 594: }