1 9 package com.vladium.util.args; 10 11 import java.io.CharArrayWriter ; 12 import java.io.IOException ; 13 import java.io.InputStream ; 14 import java.io.InputStreamReader ; 15 import java.io.PrintWriter ; 16 import java.io.Reader ; 17 import java.util.ArrayList ; 18 import java.util.HashMap ; 19 import java.util.HashSet ; 20 import java.util.Iterator ; 21 import java.util.List ; 22 import java.util.Map ; 23 import java.util.Set ; 24 25 import com.vladium.util.IConstants; 26 import com.vladium.util.ResourceLoader; 27 28 32 final class OptsParser implements IOptsParser 33 { 34 36 42 public synchronized void usage (final PrintWriter out, final int level, final int width) 43 { 44 47 final String prefix = OPT_PREFIXES [CANONICAL_OPT_PREFIX]; 48 49 for (Iterator i = m_metadata.getOptDefs (); i.hasNext (); ) 50 { 51 final OptDef optdef = (OptDef) i.next (); 52 53 if ((level < 2) && optdef.isDetailedOnly ()) continue; 55 56 final StringBuffer line = new StringBuffer (" "); 57 58 final String canonicalName = optdef.getCanonicalName (); 59 final boolean isPattern = optdef.isPattern (); 60 61 line.append (prefix); 62 line.append (canonicalName); 63 if (isPattern) line.append ('*'); 64 65 final String [] names = optdef.getNames (); 66 for (int n = 0; n < names.length; ++ n) 67 { 68 final String name = names [n]; 69 if (! name.equals (canonicalName)) 70 { 71 line.append (", "); 72 73 line.append (prefix); 74 line.append (name); 75 if (isPattern) line.append ('*'); 76 } 77 } 78 79 final String vmnemonic = optdef.getValueMnemonic (); 80 if (vmnemonic != null) 81 { 82 line.append (' '); 83 line.append (vmnemonic); 84 } 85 86 87 int padding = 16 - line.length (); 88 if (padding < 2) 89 { 90 out.println (line); 92 93 line.setLength (0); 94 for (int p = 0; p < 16; ++ p) line.append (' '); 95 } 96 else 97 { 98 for (int p = 0; p < padding; ++ p) line.append (' '); 99 } 100 101 if (optdef.isRequired ()) line.append ("{required} "); 102 line.append (optdef.getDescription ()); 103 104 out.println (line); 105 } 106 107 if (level < DETAILED_USAGE) 108 { 109 final OptDef usageOptDef = m_metadata.getUsageOptDef (); 110 if ((usageOptDef != null) && (usageOptDef.getNames () != null) && (usageOptDef.getNames ().length > 1)) 111 { 112 out.println (); 113 out.println (" {use '" + usageOptDef.getNames () [1] + "' option to see detailed usage help}"); 114 } 115 } 116 } 117 118 public synchronized IOpts parse (final String [] args) 119 { 120 if (args == null) throw new IllegalArgumentException ("null input: args"); 121 122 final Opts opts = new Opts (); 123 124 { 125 final String [] nv = new String [2]; final String [] pp = new String [1]; 128 int state = STATE_OPT; 130 OptDef optdef = null; 131 Opt opt = null; 132 String value = null; 133 int valueCount = 0; 134 135 int a; 136 scan: for (a = 0; a < args.length; ) 137 { 138 final String av = args [a]; 139 if (av == null) throw new IllegalArgumentException ("null input: args[" + a + "]"); 140 141 143 switch (state) 144 { 145 case STATE_OPT: 146 { 147 if (isOpt (av, valueCount, optdef)) 148 { 149 152 valueCount = 0; 153 154 getOptNameAndValue (av, nv); 156 158 final String optName = nv [0]; optdef = m_metadata.getOptDef (optName, pp); 161 if (optdef == null) 162 { 163 165 opts.addError (formatMessage ("unknown option \'" + optName + "\'")); 167 168 state = STATE_ERROR; 169 } 170 else 171 { 172 174 final String canonicalName = getOptCanonicalName (optName, optdef); 175 final String patternPrefix = pp [0]; 176 177 opt = opts.getOpt (canonicalName); 178 179 if (optdef.isMergeable ()) 180 { 181 if (opt == null) 182 { 183 opt = new Opt (optName, canonicalName, patternPrefix); 184 opts.addOpt (opt, optdef, optName); 185 } 186 } 187 else 188 { 189 if (opt == null) 190 { 191 opt = new Opt (optName, canonicalName, patternPrefix); 192 opts.addOpt (opt, optdef, optName); 193 } 194 else 195 { 196 opts.addError (formatMessage ("option \'" + optName + "\' cannot be specified more than once")); 197 198 state = STATE_ERROR; 199 } 200 } 201 202 value = nv [1]; 203 204 if (value == null) ++ a; 205 state = STATE_OPT_VALUE; 206 } 207 } 208 else 209 { 210 213 state = STATE_FREE_ARGS; 214 } 215 } 216 break; 217 218 219 case STATE_OPT_VALUE: 220 { 221 223 if (value != null) 224 { 225 228 valueCount = 1; 229 230 final int [] cardinality = optdef.getValueCardinality (); 231 232 if (cardinality [1] < 1) 233 { 234 opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept values: \'" + value + "\'")); 235 236 state = STATE_ERROR; 237 } 238 else 239 { 240 ++ a; 241 opt.addValue (value); 242 } 243 } 244 else 245 { 246 value = args [a]; 247 248 final int [] cardinality = optdef.getValueCardinality (); 249 250 if (isOpt (value, valueCount, optdef)) 251 { 252 if (valueCount < cardinality [0]) 253 { 254 opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)")); 255 256 state = STATE_ERROR; 257 } 258 else 259 state = STATE_OPT; 260 } 261 else 262 { 263 if (valueCount < cardinality [1]) 264 { 265 ++ valueCount; 266 ++ a; 267 opt.addValue (value); 268 } 269 else 270 { 271 state = STATE_FREE_ARGS; 280 } 281 } 282 } 283 284 value = null; 285 } 286 break; 287 288 289 case STATE_FREE_ARGS: 290 { 291 if (isOpt (args [a], valueCount, optdef)) 292 { 293 state = STATE_OPT; 294 } 295 else 296 { 297 opts.setFreeArgs (args, a); 298 break scan; 299 } 300 } 301 break; 302 303 304 case STATE_ERROR: 305 { 306 break scan; } 308 309 } } 311 312 if (a == args.length) 313 { 314 if (opt != null) { 316 final int [] cardinality = optdef.getValueCardinality (); 317 318 if (valueCount < cardinality [0]) 319 { 320 opts.addError (formatMessage ("option \'" + opt.getName () + "\' does not accept fewer than " + cardinality [0] + " value(s)")); 321 } 322 } 323 else 324 { 325 opts.setFreeArgs (args, a); 326 } 327 } 328 329 } 331 332 final IOpt [] specified = opts.getOpts (); 333 if (specified != null) 334 { 335 337 final Set required = new HashSet (); 338 required.addAll (m_metadata.getRequiredOpts ()); 339 340 for (int s = 0; s < specified.length; ++ s) 341 { 342 required.remove (specified [s].getCanonicalName ()); 343 } 344 345 if (! required.isEmpty ()) 346 { 347 for (Iterator i = required.iterator (); i.hasNext (); ) 348 { 349 opts.addError (formatMessage ("missing required option \'" + (String ) i.next () + "\'")); 350 } 351 } 352 353 for (int s = 0; s < specified.length; ++ s) 354 { 355 final IOpt opt = specified [s]; 356 final OptDef optdef = m_metadata.getOptDef (opt.getCanonicalName (), null); 357 358 366 368 final String [] requires = optdef.getRequiresSet (); if (requires != null) 370 { 371 for (int r = 0; r < requires.length; ++ r) 372 { 373 if (opts.getOpt (requires [r]) == null) 374 opts.addError (formatMessage ("option \'" + opt.getName () + "\' requires option \'" + requires [r] + "\'")); 375 } 376 } 377 378 380 final String [] excludes = optdef.getExcludesSet (); if (excludes != null) 382 { 383 for (int x = 0; x < excludes.length; ++ x) 384 { 385 final Opt xopt = opts.getOpt (excludes [x]); 386 if (xopt != null) 387 opts.addError (formatMessage ("option \'" + opt.getName () + "\' cannot be used with option \'" + xopt.getName () + "\'")); 388 } 389 } 390 391 393 if (optdef.isUsage ()) 394 { 395 opts.setUsageRequested (opt.getName ().equals (opt.getCanonicalName ()) ? SHORT_USAGE : DETAILED_USAGE); 396 } 397 } 398 } 399 400 return opts; 401 } 402 403 private static String getOptCanonicalName (final String n, final OptDef optdef) 404 { 405 if (optdef.isPattern ()) 406 { 407 final String canonicalPattern = optdef.getCanonicalName (); 408 final String [] patterns = optdef.getNames (); 409 410 for (int p = 0; p < patterns.length; ++ p) 411 { 412 final String pattern = patterns [p]; 413 414 if (n.startsWith (pattern)) 415 { 416 return canonicalPattern.concat (n.substring (pattern.length ())); 417 } 418 } 419 420 throw new IllegalStateException ("failed to detect pattern prefix for [" + n + "]"); 422 } 423 else 424 { 425 return optdef.getCanonicalName (); 426 } 427 } 428 429 435 private static boolean isOpt (final String av, final int valueCount, final OptDef optdef) 436 { 437 if (optdef != null) 438 { 439 442 final int [] cardinality = optdef.getValueCardinality (); 443 444 if (valueCount < cardinality [1]) return false; 445 } 446 447 449 for (int p = 0; p < OPT_PREFIXES.length; ++ p) 450 { 451 if (av.startsWith (OPT_PREFIXES [p])) 452 return (av.length () > OPT_PREFIXES [p].length ()); 453 } 454 455 return false; 456 } 457 458 462 private static void getOptNameAndValue (final String av, final String [] nv) 463 { 464 nv [0] = null; 465 nv [1] = null; 466 467 for (int p = 0; p < OPT_PREFIXES.length; ++ p) 468 { 469 if ((av.startsWith (OPT_PREFIXES [p])) && (av.length () > OPT_PREFIXES [p].length ())) 470 { 471 final String name = av.substring (OPT_PREFIXES [p].length ()); 473 char separator = 0; 474 int sindex = Integer.MAX_VALUE; 475 476 for (int s = 0; s < OPT_VALUE_SEPARATORS.length; ++ s) 477 { 478 final int index = name.indexOf (OPT_VALUE_SEPARATORS [s]); 479 if ((index > 0) && (index < sindex)) 480 { 481 separator = OPT_VALUE_SEPARATORS [s]; 482 sindex = index; 483 } 484 } 485 486 if (separator != 0) 487 { 488 nv [0] = name.substring (0, sindex); 489 nv [1] = name.substring (sindex + 1); 490 } 491 else 492 { 493 nv [0] = name; 494 } 495 496 return; 497 } 498 } 499 } 500 501 503 505 506 static final class Opt implements IOptsParser.IOpt 507 { 508 public String getName () 509 { 510 return m_name; 511 } 512 513 public String getCanonicalName () 514 { 515 return m_canonicalName; 516 } 517 518 public int getValueCount () 519 { 520 if (m_values == null) return 0; 521 522 return m_values.size (); 523 } 524 525 public String getFirstValue () 526 { 527 if (m_values == null) return null; 528 529 return (String ) m_values.get (0); 530 } 531 532 public String [] getValues () 533 { 534 if (m_values == null) return IConstants.EMPTY_STRING_ARRAY; 535 536 final String [] result = new String [m_values.size ()]; 537 m_values.toArray (result); 538 539 return result; 540 } 541 542 public String getPatternPrefix () 543 { 544 return m_patternPrefix; 545 } 546 547 public String toString () 548 { 549 final StringBuffer s = new StringBuffer (m_name); 550 if (! m_canonicalName.equals (m_name)) s.append (" [" + m_canonicalName + "]"); 551 552 if (m_values != null) 553 { 554 s.append (": "); 555 s.append (m_values); 556 } 557 558 return s.toString (); 559 } 560 561 Opt (final String name, final String canonicalName, final String patternPrefix) 562 { 563 m_name = name; 564 m_canonicalName = canonicalName; 565 m_patternPrefix = patternPrefix; 566 } 567 568 void addValue (final String value) 569 { 570 if (value == null) throw new IllegalArgumentException ("null input: value"); 571 572 if (m_values == null) m_values = new ArrayList (); 573 m_values.add (value); 574 } 575 576 577 private final String m_name, m_canonicalName, m_patternPrefix; 578 private ArrayList m_values; 579 580 } 582 583 static final class Opts implements IOptsParser.IOpts 584 { 585 public int usageRequestLevel () 586 { 587 return m_usageRequestLevel; 588 } 589 590 public void error (final PrintWriter out, final int width) 591 { 592 if (hasErrors ()) 594 { 595 for (Iterator i = m_errors.iterator (); i.hasNext (); ) 596 { 597 out.println (i.next ()); 598 } 599 } 600 } 601 602 public String [] getFreeArgs () 603 { 604 if (hasErrors ()) 605 throw new IllegalStateException (errorsToString ()); 606 607 return m_freeArgs; 608 } 609 610 public IOpt [] getOpts () 611 { 612 if (hasErrors ()) return null; 613 614 if (m_opts.isEmpty ()) 615 return EMPTY_OPT_ARRAY; 616 else 617 { 618 final IOpt [] result = new IOpt [m_opts.size ()]; 619 m_opts.toArray (result); 620 621 return result; 622 } 623 } 624 625 public IOpt [] getOpts (final String pattern) 626 { 627 if (hasErrors ()) return null; 628 629 final List patternOpts = (List ) m_patternMap.get (pattern); 630 631 if ((patternOpts == null) || patternOpts.isEmpty ()) 632 return EMPTY_OPT_ARRAY; 633 else 634 { 635 final IOpt [] result = new IOpt [patternOpts.size ()]; 636 patternOpts.toArray (result); 637 638 return result; 639 } 640 } 641 642 643 public boolean hasArg (final String name) 644 { 645 if (hasErrors ()) 646 throw new IllegalStateException (errorsToString ()); 647 648 return m_nameMap.containsKey (name); 649 } 650 651 Opts () 652 { 653 m_opts = new ArrayList (); 654 m_nameMap = new HashMap (); 655 m_patternMap = new HashMap (); 656 } 657 658 void addOpt (final Opt opt, final OptDef optdef, final String occuranceName) 659 { 660 if (opt == null) throw new IllegalArgumentException ("null input: opt"); 661 if (optdef == null) throw new IllegalArgumentException ("null input: optdef"); 662 if (occuranceName == null) throw new IllegalArgumentException ("null input: occuranceName"); 663 664 666 m_opts.add (opt); 667 668 final String [] names = optdef.getNames (); 669 final boolean isPattern = (opt.getPatternPrefix () != null); 670 671 if (isPattern) 672 { 673 final String unprefixedName = occuranceName.substring (opt.getPatternPrefix ().length ()); 674 675 for (int n = 0; n < names.length; ++ n) 676 { 677 m_nameMap.put (names [n].concat (unprefixedName), opt); 678 } 679 680 { 681 final String canonicalPattern = optdef.getCanonicalName (); 682 683 List patternList = (List ) m_patternMap.get (canonicalPattern); 684 if (patternList == null) 685 { 686 patternList = new ArrayList (); 687 for (int n = 0; n < names.length; ++ n) 688 { 689 m_patternMap.put (names [n], patternList); 690 } 691 } 692 693 patternList.add (opt); 694 } 695 } 696 else 697 { 698 for (int n = 0; n < names.length; ++ n) 699 { 700 m_nameMap.put (names [n], opt); 701 } 702 } 703 } 704 705 Opt getOpt (final String occuranceName) 706 { 707 if (occuranceName == null) throw new IllegalArgumentException ("null input: occuranceName"); 708 709 return (Opt) m_nameMap.get (occuranceName); 710 } 711 712 void setFreeArgs (final String [] args, final int start) 713 { 714 if (args == null) throw new IllegalArgumentException ("null input: args"); 715 if ((start < 0) || (start > args.length)) throw new IllegalArgumentException ("invalid start index: " + start); 716 717 m_freeArgs = new String [args.length - start]; 718 System.arraycopy (args, start, m_freeArgs, 0, m_freeArgs.length); 719 } 720 721 void setUsageRequested (final int level) 722 { 723 m_usageRequestLevel = level; 724 } 725 726 void addError (final String msg) 727 { 728 if (msg != null) 729 { 730 if (m_errors == null) m_errors = new ArrayList (); 731 732 m_errors.add (msg); 733 } 734 } 735 736 boolean hasErrors () 737 { 738 return (m_errors != null) && ! m_errors.isEmpty (); 739 } 740 741 String errorsToString () 742 { 743 if (! hasErrors ()) return "<no errors>"; 744 745 final CharArrayWriter caw = new CharArrayWriter (); 746 final PrintWriter pw = new PrintWriter (caw); 747 748 error (pw, DEFAULT_ERROR_WIDTH); 749 pw.flush (); 750 751 return caw.toString (); 752 } 753 754 755 private final List m_opts; 756 private final Map m_nameMap; 757 private final Map m_patternMap; 758 private String [] m_freeArgs; 759 private List m_errors; 760 private int m_usageRequestLevel; 761 762 private static final int DEFAULT_ERROR_WIDTH = 80; 763 private static final IOpt [] EMPTY_OPT_ARRAY = new IOpt [0]; 764 765 } 767 768 static final class OptDef { 770 OptDef (final boolean usage) 771 { 772 m_usage = usage; 773 } 774 775 boolean isUsage () 776 { 777 return m_usage; 778 } 779 780 String getCanonicalName () 781 { 782 return m_names [0]; 783 } 784 785 String [] getNames () 786 { 787 return m_names; 788 } 789 790 boolean isRequired () 791 { 792 return m_required; 793 } 794 795 String getValueMnemonic () 796 { 797 return m_valueMnemonic; 798 } 799 800 boolean isMergeable () 801 { 802 return m_mergeable; 803 } 804 805 boolean isDetailedOnly () 806 { 807 return m_detailedOnly; 808 } 809 810 boolean isPattern () 811 { 812 return m_pattern; 813 } 814 815 int [] getValueCardinality () 816 { 817 return m_valueCardinality; 818 } 819 820 String [] getRequiresSet () 821 { 822 return m_requiresSet; 823 } 824 825 String [] getExcludesSet () 826 { 827 return m_excludesSet; 828 } 829 830 String getDescription () 831 { 832 return m_description; 833 } 834 835 void setNames (final String [] names) 836 { 837 if (names == null) throw new IllegalArgumentException ("null input: names"); 838 839 m_names = names; 840 } 841 842 void setRequired (final boolean required) 843 { 844 m_required = required; 845 } 846 847 void setValueMnemonic (final String mnemonic) 848 { 849 if (mnemonic == null) throw new IllegalArgumentException ("null input: mnemonic"); 850 851 m_valueMnemonic = mnemonic; 852 } 853 854 void setMergeable (final boolean mergeable) 855 { 856 m_mergeable = mergeable; 857 } 858 859 void setDetailedOnly (final boolean detailedOnly) 860 { 861 m_detailedOnly = detailedOnly; 862 } 863 864 void setPattern (final boolean pattern) 865 { 866 m_pattern = pattern; 867 } 868 869 void setValueCardinality (final int [] cardinality) 870 { 871 if ((cardinality == null) || (cardinality.length != 2)) throw new IllegalArgumentException ("null or invalid input: cardinality"); 872 873 m_valueCardinality = cardinality; 874 } 875 876 void setRequiresSet (final String [] names) 877 { 878 if (names == null) throw new IllegalArgumentException ("null input: names"); 879 880 m_requiresSet = names.length > 0 ? names : null; 881 } 882 883 void setExcludesSet (final String [] names) 884 { 885 if (names == null) throw new IllegalArgumentException ("null input: names"); 886 887 m_excludesSet = names.length > 0 ? names : null; 888 } 889 890 void setDescription (final String description) 891 { 892 if (description == null) throw new IllegalArgumentException ("null input: description"); 893 894 m_description = description; 895 } 896 897 898 static final int [] C_ZERO = new int [] {0, 0}; 899 static final int [] C_ONE = new int [] {1, 1}; 900 static final int [] C_ZERO_OR_ONE = new int [] {0, 1}; 901 static final int [] C_ZERO_OR_MORE = new int [] {0, Integer.MAX_VALUE}; 902 static final int [] C_ONE_OR_MORE = new int [] {1, Integer.MAX_VALUE}; 903 904 905 private final boolean m_usage; 906 private String [] m_names; 907 private boolean m_required; 908 private String m_valueMnemonic; 909 private boolean m_mergeable; 910 private boolean m_detailedOnly; 911 private boolean m_pattern; 912 private int [] m_valueCardinality; 913 private String [] m_requiresSet, m_excludesSet; 914 private String m_description; 915 916 } 918 919 static final class OptDefMetadata 920 { 921 OptDefMetadata () 922 { 923 m_optdefs = new ArrayList (); 924 m_optdefMap = new HashMap (); 925 m_requiredOpts = new HashSet (); 926 m_patternOptDefMap = new HashMap (); 927 } 928 929 OptDef getOptDef (final String name, final String [] prefixout) 930 { 931 if (name == null) throw new IllegalArgumentException ("null input: name"); 932 933 if (prefixout != null) prefixout [0] = null; 934 935 OptDef result = (OptDef) m_optdefMap.get (name); 937 938 if (result == null) 940 { 941 for (Iterator ps = m_patternOptDefMap.entrySet ().iterator (); 942 ps.hasNext (); ) 943 { 944 final Map.Entry entry = (Map.Entry ) ps.next (); 945 final String pattern = (String ) entry.getKey (); 946 947 if (name.startsWith (pattern)) 948 { 949 if (prefixout != null) prefixout [0] = pattern; 950 result = (OptDef) entry.getValue (); 951 break; 952 } 953 } 954 } 955 956 return result; 957 } 958 959 Iterator getOptDefs () 960 { 961 return m_optdefs.iterator (); 962 } 963 964 OptDef getPatternOptDefs (final String pattern) { 966 if (pattern == null) throw new IllegalArgumentException ("null input: pattern"); 967 968 return (OptDef) m_patternOptDefMap.get (pattern); 969 } 970 971 Set getRequiredOpts () 972 { 973 return m_requiredOpts; 974 } 975 976 OptDef getUsageOptDef () 977 { 978 return m_usageOptDef; 979 } 980 981 void addOptDef (final OptDef optdef) 982 { 983 if (optdef == null) throw new IllegalArgumentException ("null input: optdef"); 984 985 final Map map = optdef.isPattern () ? m_patternOptDefMap : m_optdefMap; 986 final String [] names = optdef.getNames (); 987 988 for (int n = 0; n < names.length; ++ n) 989 { 990 if (map.containsKey (names [n])) 991 throw new IllegalArgumentException ("duplicate option name [" + names [n] + "]"); 992 993 map.put (names [n], optdef); 994 } 995 996 m_optdefs.add (optdef); 997 998 if (optdef.isRequired ()) 999 m_requiredOpts.add (optdef.getCanonicalName ()); 1000 1001 if (optdef.isUsage ()) 1002 { 1003 if (m_usageOptDef != null) 1004 throw new IllegalArgumentException ("usage optdef set already"); 1005 1006 m_usageOptDef = optdef; 1007 } 1008 } 1009 1010 1011 final List m_optdefs; final Map m_optdefMap; 1013 final Set m_requiredOpts; 1014 final Map m_patternOptDefMap; 1015 private OptDef m_usageOptDef; 1016 1017 } 1019 1020 static final class MetadataParser 1021 { 1022 1042 OptDef [] parse (final Reader in) 1043 { 1044 if (in == null) throw new IllegalArgumentException ("null input: in"); 1045 m_in = in; 1046 1047 nextChar (); 1048 nextToken (); 1049 1050 while (m_token != Token.EOF) 1051 { 1052 if (m_opts == null) m_opts = new ArrayList (); 1053 m_opts.add (optdef ()); 1054 } 1055 1056 final OptDef [] result; 1057 1058 if ((m_opts == null) || (m_opts.size () == 0)) 1059 result = EMPTY_OPTDEF_ARRAY; 1060 else 1061 { 1062 result = new OptDef [m_opts.size ()]; 1063 m_opts.toArray (result); 1064 } 1065 1066 m_in = null; 1067 m_opts = null; 1068 1069 return result; 1070 } 1071 1072 OptDef optdef () 1073 { 1074 final OptDef optdef = new OptDef (false); 1075 1076 optdef.setNames (optnamelist ()); 1077 accept (Token.COLON_ID); 1078 optmetadata (optdef); 1079 accept (Token.SEMICOLON_ID); 1080 1081 return optdef; 1082 } 1083 1084 String [] optnamelist () 1085 { 1086 return namelist (); 1087 } 1088 1089 void optmetadata (final OptDef optdef) 1090 { 1091 switch (m_token.getID ()) 1092 { 1093 case Token.REQUIRED_ID: 1094 { 1095 accept (); 1096 optdef.setRequired (true); 1097 } 1098 break; 1099 1100 case Token.OPTIONAL_ID: 1101 { 1102 accept (); 1103 optdef.setRequired (false); 1104 } 1105 break; 1106 1107 default: 1108 throw new IllegalArgumentException ("parse error: invalid token " + m_token + ", expected " + Token.REQUIRED + " or " + Token.OPTIONAL); 1109 1110 } 1112 accept (Token.COMMA_ID); 1113 1114 if (m_token.getID () == Token.MERGEABLE_ID) 1115 { 1116 accept (); 1117 optdef.setMergeable (true); 1118 1119 accept (Token.COMMA_ID); 1120 } 1121 1122 if (m_token.getID () == Token.DETAILEDONLY_ID) 1123 { 1124 accept (); 1125 optdef.setDetailedOnly (true); 1126 1127 accept (Token.COMMA_ID); 1128 } 1129 1130 if (m_token.getID () == Token.PATTERN_ID) 1131 { 1132 accept (); 1133 optdef.setPattern (true); 1134 1135 accept (Token.COMMA_ID); 1136 } 1137 1138 accept (Token.VALUES_ID); 1139 accept (Token.COLON_ID); 1140 optdef.setValueCardinality (cardinality ()); 1141 1142 accept (Token.COMMA_ID); 1143 if (m_token.getID () == Token.STRING_ID) 1144 { 1145 optdef.setValueMnemonic (m_token.getValue ()); 1146 accept (); 1147 1148 accept (Token.COMMA_ID); 1149 } 1150 1151 if (m_token.getID () == Token.REQUIRES_ID) 1152 { 1153 accept (); 1154 1155 accept (Token.LBRACKET_ID); 1156 optdef.setRequiresSet (namelist ()); 1157 accept (Token.RBRACKET_ID); 1158 1159 accept (Token.COMMA_ID); 1160 } 1161 1162 if (m_token.getID () == Token.EXCLUDES_ID) 1163 { 1164 accept (); 1165 1166 accept (Token.LBRACKET_ID); 1167 optdef.setExcludesSet (namelist ()); 1168 accept (Token.RBRACKET_ID); 1169 1170 accept (Token.COMMA_ID); 1171 } 1172 1173 optdef.setDescription (accept (Token.TEXT_ID).getValue ()); 1174 } 1175 1176 int [] cardinality () 1177 { 1178 final Token result = accept (Token.CARD_ID); 1179 1180 if ("0".equals (result.getValue ())) 1181 return OptDef.C_ZERO; 1182 else if ("1".equals (result.getValue ())) 1183 return OptDef.C_ONE; 1184 else return OptDef.C_ZERO_OR_ONE; 1186 } 1187 1188 String [] namelist () 1189 { 1190 final List _result = new ArrayList (); 1191 1192 _result.add (accept (Token.STRING_ID).getValue ()); 1193 while (m_token.getID () == Token.COMMA_ID) 1194 { 1195 accept (); 1196 _result.add (accept (Token.STRING_ID).getValue ()); 1197 } 1198 1199 final String [] result = new String [_result.size ()]; 1200 _result.toArray (result); 1201 1202 return result; 1203 } 1204 1205 1206 Token accept () 1207 { 1208 final Token current = m_token; 1209 nextToken (); 1210 1211 return current; 1212 } 1213 1214 Token accept (final int tokenID) 1215 { 1216 final Token current = m_token; 1217 1218 if (m_token.getID () == tokenID) 1219 nextToken (); 1220 else 1221 throw new IllegalArgumentException ("parse error: invalid token [" + m_token + "], expected type [" + tokenID + "]"); 1222 1223 return current; 1224 } 1225 1226 1228 void nextToken () 1229 { 1230 consumeWS (); 1231 1232 switch (m_currentChar) 1233 { 1234 case -1: m_token = Token.EOF; break; 1235 1236 case ':': 1237 { 1238 nextChar (); 1239 m_token = Token.COLON; 1240 } 1241 break; 1242 1243 case ';': 1244 { 1245 nextChar (); 1246 m_token = Token.SEMICOLON; 1247 } 1248 break; 1249 1250 case ',': 1251 { 1252 nextChar (); 1253 m_token = Token.COMMA; 1254 } 1255 break; 1256 1257 case '{': 1258 { 1259 nextChar (); 1260 m_token = Token.LBRACKET; 1261 } 1262 break; 1263 1264 case '}': 1265 { 1266 nextChar (); 1267 m_token = Token.RBRACKET; 1268 } 1269 break; 1270 1271 case '0': 1272 { 1273 nextChar (); 1274 m_token = new Token (Token.CARD_ID, "0"); 1275 } 1276 break; 1277 1278 case '1': 1279 { 1280 nextChar (); 1281 m_token = new Token (Token.CARD_ID, "1"); 1282 } 1283 break; 1284 1285 case '?': 1286 { 1287 nextChar (); 1288 m_token = new Token (Token.CARD_ID, "?"); 1289 } 1290 break; 1291 1292 case '\'': 1293 { 1294 final StringBuffer value = new StringBuffer (); 1295 1296 nextChar (); 1297 while (m_currentChar != '\'') 1298 { 1299 value.append ((char) m_currentChar); 1300 nextChar (); 1301 } 1302 nextChar (); 1303 1304 m_token = new Token (Token.STRING_ID, value.toString ()); 1305 } 1306 break; 1307 1308 case '\"': 1309 { 1310 final StringBuffer value = new StringBuffer (); 1311 1312 nextChar (); 1313 while (m_currentChar != '\"') 1314 { 1315 value.append ((char) m_currentChar); 1316 nextChar (); 1317 } 1318 nextChar (); 1319 1320 m_token = new Token (Token.TEXT_ID, value.toString ()); 1321 } 1322 break; 1323 1324 default: 1325 { 1326 final StringBuffer value = new StringBuffer (); 1327 1328 while (Character.isLetter ((char) m_currentChar)) 1329 { 1330 value.append ((char) m_currentChar); 1331 nextChar (); 1332 } 1333 1334 final Token token = (Token) KEYWORDS.get (value.toString ()); 1335 if (token == null) 1336 throw new IllegalArgumentException ("parse error: unrecognized keyword [" + value + "]"); 1337 1338 m_token = token; 1339 } 1340 1341 } } 1343 1344 1345 private void consumeWS () 1346 { 1347 if (m_currentChar == -1) 1348 return; 1349 else 1350 { 1351 while (Character.isWhitespace ((char) m_currentChar)) 1352 { 1353 nextChar (); 1354 } 1355 } 1356 1357 } 1359 1360 private void nextChar () 1361 { 1362 try 1363 { 1364 m_currentChar = m_in.read (); 1365 } 1366 catch (IOException ioe) 1367 { 1368 throw new RuntimeException ("I/O error while parsing: " + ioe); 1369 } 1370 } 1371 1372 1373 private Reader m_in; 1374 private List m_opts; 1375 1376 private Token m_token; 1377 private int m_currentChar; 1378 1379 private static final Map KEYWORDS; 1380 1381 private static final OptDef [] EMPTY_OPTDEF_ARRAY = new OptDef [0]; 1382 1383 static 1384 { 1385 KEYWORDS = new HashMap (17); 1386 1387 KEYWORDS.put (Token.OPTIONAL.getValue (), Token.OPTIONAL); 1388 KEYWORDS.put (Token.REQUIRED.getValue (), Token.REQUIRED); 1389 KEYWORDS.put (Token.VALUES.getValue (), Token.VALUES); 1390 KEYWORDS.put (Token.REQUIRES.getValue (), Token.REQUIRES); 1391 KEYWORDS.put (Token.EXCLUDES.getValue (), Token.EXCLUDES); 1392 KEYWORDS.put (Token.MERGEABLE.getValue (), Token.MERGEABLE); 1393 KEYWORDS.put (Token.DETAILEDONLY.getValue (), Token.DETAILEDONLY); 1394 KEYWORDS.put (Token.PATTERN.getValue (), Token.PATTERN); 1395 } 1396 1397 } 1399 1400 OptsParser (final String metadataResourceName, final ClassLoader loader, final String [] usageOpts) 1401 { 1402 this (metadataResourceName, loader, null, usageOpts); 1403 } 1404 1405 OptsParser (final String metadataResourceName, final ClassLoader loader, final String msgPrefix, final String [] usageOpts) 1406 { 1407 if (metadataResourceName == null) throw new IllegalArgumentException ("null input: metadataResourceName"); 1408 1409 m_msgPrefix = msgPrefix; 1410 1411 InputStream in = null; 1412 try 1413 { 1414 in = ResourceLoader.getResourceAsStream (metadataResourceName, loader); 1415 if (in == null) 1416 throw new IllegalArgumentException ("resource [" + metadataResourceName + "] could not be loaded via [" + loader + "]"); 1417 1418 final Reader rin = new InputStreamReader (in); 1420 1421 m_metadata = parseOptDefMetadata (rin, usageOpts); 1422 } 1423 finally 1424 { 1425 if (in != null) try { in.close (); } catch (IOException ignore) {} 1426 } 1427 } 1428 1429 1431 1432 private static final class Token 1433 { 1434 Token (final int ID, final String value) 1435 { 1436 if (value == null) throw new IllegalArgumentException ("null input: value"); 1437 1438 m_ID = ID; 1439 m_value = value; 1440 } 1441 1442 int getID () 1443 { 1444 return m_ID; 1445 } 1446 1447 String getValue () 1448 { 1449 return m_value; 1450 } 1451 1452 public String toString () 1453 { 1454 return m_ID + ": [" + m_value + "]"; 1455 } 1456 1457 1458 static final int EOF_ID = 0; 1459 static final int STRING_ID = 1; 1460 static final int COLON_ID = 2; 1461 static final int SEMICOLON_ID = 3; 1462 static final int COMMA_ID = 4; 1463 static final int LBRACKET_ID = 5; 1464 static final int RBRACKET_ID = 6; 1465 static final int OPTIONAL_ID = 7; 1466 static final int REQUIRED_ID = 8; 1467 static final int CARD_ID = 9; 1468 static final int VALUES_ID = 10; 1469 static final int TEXT_ID = 11; 1470 static final int REQUIRES_ID = 12; 1471 static final int EXCLUDES_ID = 13; 1472 static final int MERGEABLE_ID = 14; 1473 static final int DETAILEDONLY_ID = 15; 1474 static final int PATTERN_ID = 16; 1475 1476 static final Token EOF = new Token (EOF_ID, "<EOF>"); 1477 static final Token COLON = new Token (COLON_ID, ":"); 1478 static final Token SEMICOLON = new Token (SEMICOLON_ID, ";"); 1479 static final Token COMMA = new Token (COMMA_ID, ","); 1480 static final Token LBRACKET = new Token (LBRACKET_ID, "{"); 1481 static final Token RBRACKET = new Token (RBRACKET_ID, "}"); 1482 static final Token OPTIONAL = new Token (OPTIONAL_ID, "optional"); 1483 static final Token REQUIRED = new Token (REQUIRED_ID, "required"); 1484 static final Token VALUES = new Token (VALUES_ID, "values"); 1485 static final Token REQUIRES = new Token (REQUIRES_ID, "requires"); 1486 static final Token EXCLUDES = new Token (EXCLUDES_ID, "excludes"); 1487 static final Token MERGEABLE = new Token (MERGEABLE_ID, "mergeable"); 1488 static final Token DETAILEDONLY = new Token (DETAILEDONLY_ID, "detailedonly"); 1489 static final Token PATTERN = new Token (PATTERN_ID, "pattern"); 1490 1491 private final int m_ID; 1492 private final String m_value; 1493 1494 } 1496 1497 private static OptDefMetadata parseOptDefMetadata (final Reader in, final String [] usageOpts) 1498 { 1499 final MetadataParser parser = new MetadataParser (); 1500 final OptDef [] optdefs = parser.parse (in); 1501 1502 1504 1516 final OptDefMetadata result = new OptDefMetadata (); 1517 for (int o = 0; o < optdefs.length; ++ o) 1518 { 1519 result.addOptDef (optdefs [o]); 1520 } 1521 1522 if (usageOpts != null) 1524 { 1525 final OptDef usage = new OptDef (true); 1526 1527 usage.setNames (usageOpts); 1528 usage.setDescription ("display usage information"); 1529 usage.setValueCardinality (OptDef.C_ZERO); 1530 usage.setRequired (false); 1531 usage.setDetailedOnly (false); 1532 usage.setMergeable (false); 1533 1534 result.addOptDef (usage); 1535 } 1536 1537 1539 for (int o = 0; o < optdefs.length; ++ o) 1540 { 1541 final OptDef optdef = optdefs [o]; 1542 1543 final String [] requires = optdef.getRequiresSet (); 1544 if (requires != null) 1545 { 1546 for (int r = 0; r < requires.length; ++ r) 1547 { 1548 final OptDef ropt = result.getOptDef (requires [r], null); 1549 if (ropt == null) 1550 throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + requires [r] + "] in its \'requires\' set"); 1551 1552 if (ropt == optdef) 1553 throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'requires\' set"); 1554 } 1555 } 1556 1557 final String [] excludes = optdef.getExcludesSet (); 1558 if (excludes != null) 1559 { 1560 for (int x = 0; x < excludes.length; ++ x) 1561 { 1562 final OptDef xopt = result.getOptDef (excludes [x], null); 1563 if (xopt == null) 1564 throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies an unknown option [" + excludes [x] + "] in its \'excludes\' set"); 1565 1566 if (xopt.isRequired ()) 1567 throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies a required option [" + excludes [x] + "] in its \'excludes\' set"); 1568 1569 if (xopt == optdef) 1570 throw new IllegalArgumentException ("option [" + optdef.getCanonicalName () + "] specifies itself in its \'excludes\' set"); 1571 } 1572 } 1573 } 1574 1575 return result; 1576 } 1577 1578 private String formatMessage (final String msg) 1579 { 1580 if (m_msgPrefix == null) return msg; 1581 else 1582 { 1583 return m_msgPrefix.concat (msg); 1584 } 1585 } 1586 1587 1588 private final String m_msgPrefix; 1589 private final OptDefMetadata m_metadata; 1590 1591 private static final int CANONICAL_OPT_PREFIX = 1; private static final String [] OPT_PREFIXES = new String [] {"--", "-"}; private static final char [] OPT_VALUE_SEPARATORS = new char [] {':', '='}; 1594 1595 private static final int STATE_OPT = 0, STATE_OPT_VALUE = 1, STATE_FREE_ARGS = 2, STATE_ERROR = 3; 1596 1597}
| Popular Tags
|