1 50 package org.apache.avalon.excalibur.cli; 51 52 import java.text.ParseException ; 53 import java.util.Hashtable ; 54 import java.util.Vector ; 55 56 75 public final class CLArgsParser 76 { 77 private static final int INVALID = Integer.MAX_VALUE; 79 80 private static final int STATE_NORMAL = 0; 81 private static final int STATE_REQUIRE_2ARGS = 1; 82 private static final int STATE_REQUIRE_ARG = 2; 83 private static final int STATE_OPTIONAL_ARG = 3; 84 private static final int STATE_NO_OPTIONS = 4; 85 private static final int STATE_OPTION_MODE = 5; 86 87 private static final int TOKEN_SEPARATOR = 0; 88 private static final int TOKEN_STRING = 1; 89 90 private static final char[] ARG2_SEPARATORS = 91 new char[]{(char)0, '=', '-'}; 92 93 private static final char[] ARG_SEPARATORS = 94 new char[]{(char)0, '='}; 95 96 private static final char[] NULL_SEPARATORS = 97 new char[]{(char)0}; 98 99 private final CLOptionDescriptor[] m_optionDescriptors; 100 private final Vector m_options; 101 private Hashtable m_optionIndex; 102 private final ParserControl m_control; 103 104 private String m_errorMessage; 105 private String [] m_unparsedArgs = new String []{}; 106 107 private char m_ch; 109 private String [] m_args; 110 private boolean m_isLong; 111 private int m_argIndex; 112 private int m_stringIndex; 113 private int m_stringLength; 114 115 private int m_lastChar = INVALID; 116 117 private int m_lastOptionId; 118 private CLOption m_option; 119 private int m_state = STATE_NORMAL; 120 121 127 public final String [] getUnparsedArgs() 128 { 129 return m_unparsedArgs; 130 } 131 132 137 public final Vector getArguments() 138 { 139 return m_options; 141 } 142 143 152 public final CLOption getArgumentById( final int id ) 153 { 154 return (CLOption)m_optionIndex.get( new Integer ( id ) ); 155 } 156 157 166 public final CLOption getArgumentByName( final String name ) 167 { 168 return (CLOption)m_optionIndex.get( name ); 169 } 170 171 177 private final CLOptionDescriptor getDescriptorFor( final int id ) 178 { 179 for( int i = 0; i < m_optionDescriptors.length; i++ ) 180 { 181 if( m_optionDescriptors[ i ].getId() == id ) 182 { 183 return m_optionDescriptors[ i ]; 184 } 185 } 186 187 return null; 188 } 189 190 196 private final CLOptionDescriptor getDescriptorFor( final String name ) 197 { 198 for( int i = 0; i < m_optionDescriptors.length; i++ ) 199 { 200 if( m_optionDescriptors[ i ].getName().equals( name ) ) 201 { 202 return m_optionDescriptors[ i ]; 203 } 204 } 205 206 return null; 207 } 208 209 214 public final String getErrorString() 215 { 216 return m_errorMessage; 218 } 219 220 226 private final int getStateFor( final CLOptionDescriptor descriptor ) 227 { 228 final int flags = descriptor.getFlags(); 229 if( ( flags & CLOptionDescriptor.ARGUMENTS_REQUIRED_2 ) == 230 CLOptionDescriptor.ARGUMENTS_REQUIRED_2 ) 231 { 232 return STATE_REQUIRE_2ARGS; 233 } 234 else if( ( flags & CLOptionDescriptor.ARGUMENT_REQUIRED ) == 235 CLOptionDescriptor.ARGUMENT_REQUIRED ) 236 { 237 return STATE_REQUIRE_ARG; 238 } 239 else if( ( flags & CLOptionDescriptor.ARGUMENT_OPTIONAL ) == 240 CLOptionDescriptor.ARGUMENT_OPTIONAL ) 241 { 242 return STATE_OPTIONAL_ARG; 243 } 244 else 245 { 246 return STATE_NORMAL; 247 } 248 } 249 250 258 public CLArgsParser( final String [] args, 259 final CLOptionDescriptor[] optionDescriptors, 260 final ParserControl control ) 261 { 262 m_optionDescriptors = optionDescriptors; 263 m_control = control; 264 m_options = new Vector (); 265 m_args = args; 266 267 try 268 { 269 parse(); 270 checkIncompatibilities( m_options ); 271 buildOptionIndex(); 272 } 273 catch( final ParseException pe ) 274 { 275 m_errorMessage = pe.getMessage(); 276 } 277 278 } 281 282 288 private final void checkIncompatibilities( final Vector arguments ) 289 throws ParseException 290 { 291 final int size = arguments.size(); 292 293 for( int i = 0; i < size; i++ ) 294 { 295 final CLOption option = (CLOption)arguments.elementAt( i ); 296 final int id = option.getDescriptor().getId(); 297 final CLOptionDescriptor descriptor = getDescriptorFor( id ); 298 299 if( null == descriptor ) 302 { 303 continue; 304 } 305 306 final int[] incompatible = descriptor.getIncompatible(); 307 308 checkIncompatible( arguments, incompatible, i ); 309 } 310 } 311 312 private final void checkIncompatible( final Vector arguments, 313 final int[] incompatible, 314 final int original ) 315 throws ParseException 316 { 317 final int size = arguments.size(); 318 319 for( int i = 0; i < size; i++ ) 320 { 321 if( original == i ) 322 { 323 continue; 324 } 325 326 final CLOption option = (CLOption)arguments.elementAt( i ); 327 final int id = option.getDescriptor().getId(); 328 329 for( int j = 0; j < incompatible.length; j++ ) 330 { 331 if( id == incompatible[ j ] ) 332 { 333 final CLOption originalOption = (CLOption)arguments.elementAt( original ); 334 final int originalId = originalOption.getDescriptor().getId(); 335 336 String message = null; 337 338 if( id == originalId ) 339 { 340 message = 341 "Duplicate options for " + describeDualOption( originalId ) + 342 " found."; 343 } 344 else 345 { 346 message = "Incompatible options -" + 347 describeDualOption( id ) + " and " + 348 describeDualOption( originalId ) + " found."; 349 } 350 throw new ParseException ( message, 0 ); 351 } 352 } 353 } 354 } 355 356 private final String describeDualOption( final int id ) 357 { 358 final CLOptionDescriptor descriptor = getDescriptorFor( id ); 359 if( null == descriptor ) 360 { 361 return "<parameter>"; 362 } 363 else 364 { 365 final StringBuffer sb = new StringBuffer (); 366 boolean hasCharOption = false; 367 368 if( Character.isLetter( (char)id ) ) 369 { 370 sb.append( '-' ); 371 sb.append( (char)id ); 372 hasCharOption = true; 373 } 374 375 final String longOption = descriptor.getName(); 376 if( null != longOption ) 377 { 378 if( hasCharOption ) 379 { 380 sb.append( '/' ); 381 } 382 sb.append( "--" ); 383 sb.append( longOption ); 384 } 385 386 return sb.toString(); 387 } 388 } 389 390 396 public CLArgsParser( final String [] args, 397 final CLOptionDescriptor[] optionDescriptors ) 398 { 399 this( args, optionDescriptors, null ); 400 } 401 402 412 private final String [] subArray( final String [] array, 413 final int index, 414 final int charIndex ) 415 { 416 final int remaining = array.length - index; 417 final String [] result = new String [ remaining ]; 418 419 if( remaining > 1 ) 420 { 421 System.arraycopy( array, index + 1, result, 1, remaining - 1 ); 422 } 423 424 result[ 0 ] = array[ index ].substring( charIndex - 1 ); 425 426 return result; 427 } 428 429 432 private final void parse() 433 throws ParseException 434 { 435 if( 0 == m_args.length ) 436 { 437 return; 438 } 439 440 m_stringLength = m_args[ m_argIndex ].length(); 441 442 444 while( true ) 445 { 446 m_ch = peekAtChar(); 447 448 451 if( m_argIndex >= m_args.length ) 452 { 453 break; 454 } 455 456 if( null != m_control && m_control.isFinished( m_lastOptionId ) ) 457 { 458 m_unparsedArgs = subArray( m_args, m_argIndex, m_stringIndex ); 460 return; 461 } 462 463 466 if( STATE_OPTION_MODE == m_state ) 467 { 468 if( 0 == m_ch ) 471 { 472 getChar(); m_state = STATE_NORMAL; 474 } 475 else 476 { 477 parseShortOption(); 478 } 479 } 480 else if( STATE_NORMAL == m_state ) 481 { 482 parseNormal(); 483 } 484 else if( STATE_NO_OPTIONS == m_state ) 485 { 486 addOption( new CLOption( m_args[ m_argIndex++ ] ) ); 488 } 489 else if( STATE_OPTIONAL_ARG == m_state && '-' == m_ch ) 490 { 491 m_state = STATE_NORMAL; 492 addOption( m_option ); 493 } 494 else 495 { 496 parseArguments(); 497 } 498 } 499 500 if( m_option != null ) 501 { 502 if( STATE_OPTIONAL_ARG == m_state ) 503 { 504 m_options.addElement( m_option ); 505 } 506 else if( STATE_REQUIRE_ARG == m_state ) 507 { 508 final CLOptionDescriptor descriptor = getDescriptorFor( m_option.getDescriptor().getId() ); 509 final String message = 510 "Missing argument to option " + getOptionDescription( descriptor ); 511 throw new ParseException ( message, 0 ); 512 } 513 else if( STATE_REQUIRE_2ARGS == m_state ) 514 { 515 if( 1 == m_option.getArgumentCount() ) 516 { 517 m_option.addArgument( "" ); 518 m_options.addElement( m_option ); 519 } 520 else 521 { 522 final CLOptionDescriptor descriptor = getDescriptorFor( m_option.getDescriptor().getId() ); 523 final String message = 524 "Missing argument to option " + getOptionDescription( descriptor ); 525 throw new ParseException ( message, 0 ); 526 } 527 } 528 else 529 { 530 throw new ParseException ( "IllegalState " + m_state + ": " + m_option, 0 ); 531 } 532 } 533 } 534 535 private final String getOptionDescription( final CLOptionDescriptor descriptor ) 536 { 537 if( m_isLong ) 538 { 539 return "--" + descriptor.getName(); 540 } 541 else 542 { 543 return "-" + (char)descriptor.getId(); 544 } 545 } 546 547 private final char peekAtChar() 548 { 549 if( INVALID == m_lastChar ) 550 { 551 m_lastChar = readChar(); 552 } 553 return (char)m_lastChar; 554 } 555 556 private final char getChar() 557 { 558 if( INVALID != m_lastChar ) 559 { 560 final char result = (char)m_lastChar; 561 m_lastChar = INVALID; 562 return result; 563 } 564 else 565 { 566 return readChar(); 567 } 568 } 569 570 private final char readChar() 571 { 572 if( m_stringIndex >= m_stringLength ) 573 { 574 m_argIndex++; 575 m_stringIndex = 0; 576 577 if( m_argIndex < m_args.length ) 578 { 579 m_stringLength = m_args[ m_argIndex ].length(); 580 } 581 else 582 { 583 m_stringLength = 0; 584 } 585 586 return 0; 587 } 588 589 if( m_argIndex >= m_args.length ) 590 { 591 return 0; 592 } 593 594 return m_args[ m_argIndex ].charAt( m_stringIndex++ ); 595 } 596 597 private final Token nextToken( final char[] separators ) 598 { 599 m_ch = getChar(); 600 601 if( isSeparator( m_ch, separators ) ) 602 { 603 m_ch = getChar(); 604 return new Token( TOKEN_SEPARATOR, null ); 605 } 606 607 final StringBuffer sb = new StringBuffer (); 608 609 do 610 { 611 sb.append( m_ch ); 612 m_ch = getChar(); 613 } while( !isSeparator( m_ch, separators ) ); 614 615 return new Token( TOKEN_STRING, sb.toString() ); 616 } 617 618 private final boolean isSeparator( final char ch, final char[] separators ) 619 { 620 for( int i = 0; i < separators.length; i++ ) 621 { 622 if( ch == separators[ i ] ) 623 { 624 return true; 625 } 626 } 627 628 return false; 629 } 630 631 private final void addOption( final CLOption option ) 632 { 633 m_options.addElement( option ); 634 m_lastOptionId = option.getDescriptor().getId(); 635 m_option = null; 636 } 637 638 private final void parseOption( final CLOptionDescriptor descriptor, 639 final String optionString ) 640 throws ParseException 641 { 642 if( null == descriptor ) 643 { 644 throw new ParseException ( "Unknown option " + optionString, 0 ); 645 } 646 647 m_state = getStateFor( descriptor ); 648 m_option = new CLOption( descriptor ); 649 650 if( STATE_NORMAL == m_state ) 651 { 652 addOption( m_option ); 653 } 654 } 655 656 private final void parseShortOption() 657 throws ParseException 658 { 659 m_ch = getChar(); 660 final CLOptionDescriptor descriptor = getDescriptorFor( m_ch ); 661 m_isLong = false; 662 parseOption( descriptor, "-" + m_ch ); 663 664 if( STATE_NORMAL == m_state ) 665 { 666 m_state = STATE_OPTION_MODE; 667 } 668 } 669 670 private final void parseArguments() 671 throws ParseException 672 { 673 if( STATE_REQUIRE_ARG == m_state ) 674 { 675 if( '=' == m_ch || 0 == m_ch ) 676 { 677 getChar(); 678 } 679 680 final Token token = nextToken( NULL_SEPARATORS ); 681 m_option.addArgument( token.getValue() ); 682 683 addOption( m_option ); 684 m_state = STATE_NORMAL; 685 } 686 else if( STATE_OPTIONAL_ARG == m_state ) 687 { 688 if( '-' == m_ch || 0 == m_ch ) 689 { 690 getChar(); addOption( m_option ); 692 m_state = STATE_NORMAL; 693 return; 694 } 695 696 if( '=' == m_ch ) 697 { 698 getChar(); 699 } 700 701 final Token token = nextToken( NULL_SEPARATORS ); 702 m_option.addArgument( token.getValue() ); 703 704 addOption( m_option ); 705 m_state = STATE_NORMAL; 706 } 707 else if( STATE_REQUIRE_2ARGS == m_state ) 708 { 709 if( 0 == m_option.getArgumentCount() ) 710 { 711 final Token token = nextToken( ARG_SEPARATORS ); 712 713 if( TOKEN_SEPARATOR == token.getType() ) 714 { 715 final CLOptionDescriptor descriptor = getDescriptorFor( m_option.getDescriptor().getId() ); 716 final String message = 717 "Unable to parse first argument for option " + 718 getOptionDescription( descriptor ); 719 throw new ParseException ( message, 0 ); 720 } 721 else 722 { 723 m_option.addArgument( token.getValue() ); 724 } 725 } 726 else { 728 final StringBuffer sb = new StringBuffer (); 729 730 m_ch = getChar(); 731 if( '-' == m_ch ) 732 { 733 m_lastChar = m_ch; 734 } 735 736 while( !isSeparator( m_ch, ARG2_SEPARATORS ) ) 737 { 738 sb.append( m_ch ); 739 m_ch = getChar(); 740 } 741 742 final String argument = sb.toString(); 743 744 746 m_option.addArgument( argument ); 747 addOption( m_option ); 748 m_option = null; 749 m_state = STATE_NORMAL; 750 } 751 } 752 } 753 754 757 private final void parseNormal() 758 throws ParseException 759 { 760 if( '-' != m_ch ) 761 { 762 final String argument = nextToken( NULL_SEPARATORS ).getValue(); 764 addOption( new CLOption( argument ) ); 765 m_state = STATE_NORMAL; 766 } 767 else 768 { 769 getChar(); 771 if( 0 == peekAtChar() ) 772 { 773 throw new ParseException ( "Malformed option -", 0 ); 774 } 775 else 776 { 777 m_ch = peekAtChar(); 778 779 if( '-' != m_ch ) 781 { 782 parseShortOption(); 783 } 784 else 785 { 786 getChar(); 790 if( 0 == peekAtChar() ) 791 { 792 getChar(); 793 m_state = STATE_NO_OPTIONS; 794 } 795 else 796 { 797 final String optionName = nextToken( ARG_SEPARATORS ).getValue(); 799 final CLOptionDescriptor descriptor = getDescriptorFor( optionName ); 800 m_isLong = true; 801 parseOption( descriptor, "--" + optionName ); 802 } 803 } 804 } 805 } 806 } 807 808 811 private final void buildOptionIndex() 812 { 813 final int size = m_options.size(); 814 m_optionIndex = new Hashtable ( size * 2 ); 815 816 for( int i = 0; i < size; i++ ) 817 { 818 final CLOption option = (CLOption)m_options.get( i ); 819 final CLOptionDescriptor optionDescriptor = 820 getDescriptorFor( option.getDescriptor().getId() ); 821 822 m_optionIndex.put( new Integer ( option.getDescriptor().getId() ), option ); 823 824 if( null != optionDescriptor && 825 null != optionDescriptor.getName() ) 826 { 827 m_optionIndex.put( optionDescriptor.getName(), option ); 828 } 829 } 830 } 831 } 832 | Popular Tags |