Source for net.dpml.cli.option.ArgumentImpl

   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: }