0001: /**
0002: * Copyright John E. Lloyd, 2004. All rights reserved. Permission to use,
0003: * copy, modify and redistribute is granted, provided that this copyright
0004: * notice is retained and the author is given credit whenever appropriate.
0005: *
0006: * This software is distributed "as is", without any warranty, including
0007: * any implied warranty of merchantability or fitness for a particular
0008: * use. The author assumes no responsibility for, and shall not be liable
0009: * for, any special, indirect, or consequential damages, or any damages
0010: * whatsoever, arising out of or in connection with the use of this
0011: * software.
0012: */package argparser;
0013:
0014: import java.io.PrintStream;
0015: import java.io.IOException;
0016: import java.io.LineNumberReader;
0017: import java.io.File;
0018: import java.io.FileReader;
0019: import java.io.Reader;
0020: import java.util.Vector;
0021:
0022: import java.lang.reflect.Array;
0023:
0024: /**
0025: * ArgParser is used to parse the command line arguments for a java
0026: * application program. It provides a compact way to specify options and match
0027: * them against command line arguments, with support for
0028: * <a href=#rangespec>range checking</a>,
0029: * <a href=#multipleOptionNames>multiple option names</a> (aliases),
0030: * <a href=#singleWordOptions>single word options</a>,
0031: * <a href=#multipleOptionValues>multiple values associated with an option</a>,
0032: * <a href=#multipleOptionInvocation>multiple option invocation</a>,
0033: * <a href=#helpInfo>generating help information</a>,
0034: * <a href=#customArgParsing>custom argument parsing</a>, and
0035: * <a href=#argsFromAFile>reading arguments from a file</a>. The
0036: * last feature is particularly useful and makes it
0037: * easy to create ad-hoc configuration files for an application.
0038: *
0039: * <h3><a name="example">Basic Example</a></h3>
0040: *
0041: * <p>Here is a simple example in which an application has three
0042: * command line options:
0043: * <code>-theta</code> (followed by a floating point value),
0044: * <code>-file</code> (followed by a string value), and
0045: * <code>-debug</code>, which causes a boolean value to be set.
0046: *
0047: * <pre>
0048: *
0049: * static public void main (String[] args)
0050: * {
0051: * // create holder objects for storing results ...
0052: *
0053: * DoubleHolder theta = new DoubleHolder();
0054: * StringHolder fileName = new StringHolder();
0055: * BooleanHolder debug = new BooleanHolder();
0056: *
0057: * // create the parser and specify the allowed options ...
0058: *
0059: * ArgParser parser = new ArgParser("java argparser.SimpleExample");
0060: * parser.addOption ("-theta %f #theta value (in degrees)", theta);
0061: * parser.addOption ("-file %s #name of the operating file", fileName);
0062: * parser.addOption ("-debug %v #enables display of debugging info", debug);
0063: *
0064: * // match the arguments ...
0065: *
0066: * parser.matchAllArgs (args);
0067: *
0068: * // and print out the values
0069: *
0070: * System.out.println ("theta=" + theta.value);
0071: * System.out.println ("fileName=" + fileName.value);
0072: * System.out.println ("debug=" + debug.value);
0073: * }
0074: * </pre>
0075: * <p>A command line specifying all three options might look like this:
0076: * <pre>
0077: * java argparser.SimpleExample -theta 7.8 -debug -file /ai/lloyd/bar
0078: * </pre>
0079: *
0080: * <p>The application creates an instance of ArgParser and then adds
0081: * descriptions of the allowed options using {@link #addOption addOption}. The
0082: * method {@link #matchAllArgs(String[]) matchAllArgs} is then used to match
0083: * these options against the command line arguments. Values associated with
0084: * each option are returned in the <code>value</code> field of special
0085: * ``holder'' classes (e.g., {@link argparser.DoubleHolder DoubleHolder},
0086: * {@link argparser.StringHolder StringHolder}, etc.).
0087: *
0088: * <p> The first argument to {@link #addOption addOption} is a string that
0089: * specifies (1) the option's name, (2) a conversion code for its associated
0090: * value (e.g., <code>%f</code> for floating point, <code>%s</code> for a
0091: * string, <code>%v</code> for a boolean flag), and (3) an optional description
0092: * (following the <code>#</code> character) which is used for generating help
0093: * messages. The second argument is the holder object through which the value
0094: * is returned. This may be either a type-specific object (such as {@link
0095: * argparser.DoubleHolder DoubleHolder} or {@link argparser.StringHolder
0096: * StringHolder}), an array of the appropriate type, or
0097: * <a href=#multipleOptionInvocation> an instance of
0098: * <code>java.util.Vector</code></a>.
0099: *
0100: * <p>By default, arguments that don't match the specified options, are <a
0101: * href=#rangespec>out of range</a>, or are otherwise formatted incorrectly,
0102: * will cause <code>matchAllArgs</code> to print a message and exit the
0103: * program. Alternatively, an application can use {@link
0104: * #matchAllArgs(String[],int,int) matchAllArgs(args,idx,exitFlags)} to obtain
0105: * an array of unmatched arguments which can then be
0106: * <a href=#customArgParsing>processed separately</a>
0107: *
0108: * <h3><a name="rangespec">Range Specification</a></h3>
0109: *
0110: * The values associated with options can also be given range specifications. A
0111: * range specification appears in curly braces immediately following the
0112: * conversion code. In the code fragment below, we show how to specify an
0113: * option <code>-name</code> that expects to be provided with one of three
0114: * string values (<code>john</code>, <code>mary</code>, or <code>jane</code>),
0115: * an option <code>-index</code> that expects to be supplied with a integer
0116: * value in the range 1 to 256, an option <code>-size</code> that expects to be
0117: * supplied with integer values of either 1, 2, 4, 8, or 16, and an option
0118: * <code>-foo</code> that expects to be supplied with floating point values in
0119: * the ranges -99 < foo <= -50, or 50 <= foo < 99.
0120: *
0121: * <pre>
0122: * StringHolder name = new StringHolder();
0123: * IntHolder index = new IntHolder();
0124: * IntHolder size = new IntHolder();
0125: * DoubleHolder foo = new DoubleHolder();
0126: *
0127: * parser.addOption ("-name %s {john,mary,jane}", name);
0128: * parser.addOption ("-index %d {[1,256]}", index);
0129: * parser.addOption ("-size %d {1,2,4,8,16}", size);
0130: * parser.addOption ("-foo %f {(-99,-50],[50,99)}", foo);
0131: * </pre>
0132: *
0133: * If an argument value does not lie within a specified range, an error is
0134: * generated.
0135: *
0136: * <h3><a name="multipleOptionNames">Multiple Option Names</a></h3>
0137: *
0138: * An option may be given several names, or aliases, in the form of
0139: * a comma seperated list:
0140: *
0141: * <pre>
0142: * parser.addOption ("-v,--verbose %v #print lots of info");
0143: * parser.addOption ("-of,-outfile,-outputFile %s #output file");
0144: * </pre>
0145: *
0146: * <h3><a name="singleWordOptions">Single Word Options</a></h3>
0147: *
0148: * Normally, options are assumed to be "multi-word", meaning
0149: * that any associated value must follow the option as a
0150: * separate argument string. For
0151: * example,
0152: * <pre>
0153: * parser.addOption ("-file %s #file name");
0154: * </pre>
0155: * will cause the parser to look for two strings in the argument list
0156: * of the form
0157: * <pre>
0158: * -file someFileName
0159: * </pre>
0160: * However, if there is no white space separting the option's name from
0161: * it's conversion code, then values associated with that
0162: * option will be assumed to be part of the same argument
0163: * string as the option itself. For example,
0164: * <pre>
0165: * parser.addOption ("-file=%s #file name");
0166: * </pre>
0167: * will cause the parser to look for a single string in the argument
0168: * list of the form
0169: * <pre>
0170: * -file=someFileName
0171: * </pre>
0172: * Such an option is called a "single word" option.
0173: *
0174: * <p>
0175: * In cases where an option has multiple names, then this single
0176: * word behavior is invoked if there is no white space between
0177: * the last indicated name and the conversion code. However, previous
0178: * names in the list will still be given multi-word behavior
0179: * if there is white space between the name and the
0180: * following comma. For example,
0181: * <pre>
0182: * parser.addOption ("-nb=,-number ,-n%d #number of blocks");
0183: * </pre>
0184: * will cause the parser to look for one, two, and one word constructions
0185: * of the forms
0186: * <pre>
0187: * -nb=N
0188: * -number N
0189: * -nN
0190: * </pre>
0191: *
0192: * <h3><a name="multipleOptionValues">Multiple Option Values</a></h3>
0193: *
0194: * If may be useful for an option to be followed by several values.
0195: * For instance, we might have an option <code>-velocity</code>
0196: * which should be followed by three numbers denoting
0197: * the x, y, and z components of a velocity vector.
0198: * We can require multiple values for an option
0199: * by placing a <i>multiplier</i> specification,
0200: * of the form <code>X</code>N, where N is an integer,
0201: * after the conversion code (or range specification, if present).
0202: * For example,
0203: *
0204: * <pre>
0205: * double[] pos = new double[3];
0206: *
0207: * addOption ("-position %fX3 #position of the object", pos);
0208: * </pre>
0209: * will cause the parser to look for
0210: * <pre>
0211: * -position xx yy zz
0212: * </pre>
0213: *
0214: * in the argument list, where <code>xx</code>, <code>yy</code>, and
0215: * <code>zz</code> are numbers. The values are stored in the array
0216: * <code>pos</code>.
0217: *
0218: * Options requiring multiple values must use arrays to
0219: * return their values, and cannot be used in single word format.
0220: *
0221: * <h3><a name="multipleOptionInvocation">Multiple Option Invocation</a></h3>
0222: *
0223: * Normally, if an option appears twice in the command list, the
0224: * value associated with the second instance simply overwrites the
0225: * value associated with the first instance.
0226: *
0227: * However, the application can instead arrange for the storage of <i>all</i>
0228: * values associated with multiple option invocation, by supplying a instance
0229: * of <code>java.util.Vector</code> to serve as the value holder. Then every
0230: * time the option appears in the argument list, the parser will create a value
0231: * holder of appropriate type, set it to the current value, and store the
0232: * holder in the vector. For example, the construction
0233: *
0234: * <pre>
0235: * Vector vec = new Vector(10);
0236: *
0237: * parser.addOption ("-foo %f", vec);
0238: * parser.matchAllArgs(args);
0239: * </pre>
0240: * when supplied with an argument list that contains
0241: * <pre>
0242: * -foo 1.2 -foo 1000 -foo -78
0243: * </pre>
0244: *
0245: * will create three instances of {@link argparser.DoubleHolder DoubleHolder},
0246: * initialized to <code>1.2</code>, <code>1000</code>, and <code>-78</code>,
0247: * and store them in <code>vec</code>.
0248: *
0249: * <h3><a name="helpInfo">Generating help information</a></h3>
0250: *
0251: * ArgParser automatically generates help information for the options, and this
0252: * information may be printed in response to a <i>help</i> option, or may be
0253: * queried by the application using {@link #getHelpMessage getHelpMessage}.
0254: * The information for each option consists of the option's name(s), it's
0255: * required value(s), and an application-supplied description. Value
0256: * information is generated automaticlly from the conversion code, range, and
0257: * multiplier specifications (although this can be overriden, as
0258: * <a href=#valueInfo>described below</a>).
0259: * The application-supplied description is whatever
0260: * appears in the specification string after the optional <code>#</code>
0261: * character. The string returned by {@link #getHelpMessage getHelpMessage} for
0262: * the <a href=#example>first example above</a> would be
0263: *
0264: * <pre>
0265: * Usage: java argparser.SimpleExample
0266: * Options include:
0267: *
0268: * -help,-? displays help information
0269: * -theta <float> theta value (in degrees)
0270: * -file <string> name of the operating file
0271: * -debug enables display of debugging info
0272: * </pre>
0273: *
0274: * The options <code>-help</code> and <code>-?</code> are including in the
0275: * parser by default as help options, and they automatically cause the help
0276: * message to be printed. To exclude these
0277: * options, one should use the constructor {@link #ArgParser(String,boolean)
0278: * ArgParser(synopsis,false)}.
0279: * Help options can also be specified by the application using {@link
0280: * #addOption addOption} and the conversion code <code>%h</code>. Help options
0281: * can be disabled using {@link #setHelpOptionsEnabled
0282: * setHelpOptionsEnabled(false)}.
0283: *
0284: * <p><a name=valueInfo>
0285: * A description of the required values for an option can be
0286: * specified explicitly
0287: * by placing a second <code>#</code> character in the specification
0288: * string. Everything between the first and second <code>#</code>
0289: * characters then becomes the value description, and everything
0290: * after the second <code>#</code> character becomes the option
0291: * description.
0292: * For example, if the <code>-theta</code> option
0293: * above was specified with
0294: * <pre>
0295: * parser.addOption ("-theta %f #NUMBER#theta value (in degrees)",theta);
0296: * </pre>
0297: * instead of
0298: * <pre>
0299: * parser.addOption ("-theta %f #theta value (in degrees)", theta);
0300: * </pre>
0301: * then the corresponding entry in the help message would look
0302: * like
0303: * <pre>
0304: * -theta NUMBER theta value (in degrees)
0305: * </pre>
0306: *
0307: * <h3><a name="customArgParsing">Custom Argument Parsing</a></h3>
0308: *
0309: * An application may find it necessary to handle arguments that
0310: * don't fit into the framework of this class. There are a couple
0311: * of ways to do this.
0312: *
0313: * <p>
0314: * First, the method {@link #matchAllArgs(String[],int,int)
0315: * matchAllArgs(args,idx,exitFlags)} returns an array of
0316: * all unmatched arguments, which can then be handled
0317: * specially:
0318: * <pre>
0319: * String[] unmatched =
0320: * parser.matchAllArgs (args, 0, parser.EXIT_ON_ERROR);
0321: * for (int i = 0; i < unmatched.length; i++)
0322: * { ... handle unmatched arguments ...
0323: * }
0324: * </pre>
0325: *
0326: * For instance, this would be useful for an applicatoon that accepts an
0327: * arbitrary number of input file names. The options can be parsed using
0328: * <code>matchAllArgs</code>, and the remaining unmatched arguments
0329: * give the file names.
0330: *
0331: * <p> If we need more control over the parsing, we can parse arguments one at
0332: * a time using {@link #matchArg matchArg}:
0333: *
0334: * <pre>
0335: * int idx = 0;
0336: * while (idx < args.length)
0337: * { try
0338: * { idx = parser.matchArg (args, idx);
0339: * if (parser.getUnmatchedArgument() != null)
0340: * {
0341: * ... handle this unmatched argument ourselves ...
0342: * }
0343: * }
0344: * catch (ArgParserException e)
0345: * { // malformed or erroneous argument
0346: * parser.printErrorAndExit (e.getMessage());
0347: * }
0348: * }
0349: * </pre>
0350: *
0351: * {@link #matchArg matchArg(args,idx)} matches one option at location
0352: * <code>idx</code> in the argument list, and then returns the location value
0353: * that should be used for the next match. If an argument does
0354: * not match any option,
0355: * {@link #getUnmatchedArgument getUnmatchedArgument} will return a copy of the
0356: * unmatched argument.
0357: *
0358: * <h3><a name="argsFromAFile">Reading Arguments From a File</a></h3>
0359: *
0360: * The method {@link #prependArgs prependArgs} can be used to automatically
0361: * read in a set of arguments from a file and prepend them onto an existing
0362: * argument list. Argument words correspond to white-space-delimited strings,
0363: * and the file may contain the comment character <code>#</code> (which
0364: * comments out everything to the end of the current line). A typical usage
0365: * looks like this:
0366: *
0367: * <pre>
0368: * ... create parser and add options ...
0369: *
0370: * args = parser.prependArgs (new File(".configFile"), args);
0371: *
0372: * parser.matchAllArgs (args);
0373: * </pre>
0374: *
0375: * This makes it easy to generate simple configuration files for an
0376: * application.
0377: *
0378: * @author John E. Lloyd, Fall 2004
0379: */
0380: public class ArgParser {
0381: Vector matchList;
0382: // int tabSpacing = 8;
0383: String synopsisString;
0384: boolean helpOptionsEnabled = true;
0385: Record defaultHelpOption = null;
0386: Record firstHelpOption = null;
0387: PrintStream printStream = System.out;
0388: int helpIndent = 24;
0389: String errMsg = null;
0390: String unmatchedArg = null;
0391:
0392: static String validConversionCodes = "iodxcbfsvh";
0393:
0394: /**
0395: * Indicates that the program should exit with an appropriate message
0396: * in the event of an erroneous or malformed argument.*/
0397: public static int EXIT_ON_ERROR = 1;
0398:
0399: /**
0400: * Indicates that the program should exit with an appropriate message
0401: * in the event of an unmatched argument.*/
0402: public static int EXIT_ON_UNMATCHED = 2;
0403:
0404: /**
0405: * Returns a string containing the valid conversion codes. These
0406: * are the characters which may follow the <code>%</code> character in
0407: * the specification string of {@link #addOption addOption}.
0408: *
0409: * @return Valid conversion codes
0410: * @see #addOption
0411: */
0412: public static String getValidConversionCodes() {
0413: return validConversionCodes;
0414: }
0415:
0416: static class NameDesc {
0417: String name;
0418: // oneWord implies that any value associated with
0419: // option is concatenated onto the argument string itself
0420: boolean oneWord;
0421: NameDesc next = null;
0422: }
0423:
0424: static class RangePnt {
0425: double dval = 0;
0426: long lval = 0;
0427: String sval = null;
0428: boolean bval = true;
0429: boolean closed = true;
0430:
0431: RangePnt(String s, boolean closed) {
0432: sval = s;
0433: this .closed = closed;
0434: }
0435:
0436: RangePnt(double d, boolean closed) {
0437: dval = d;
0438: this .closed = closed;
0439: }
0440:
0441: RangePnt(long l, boolean closed) {
0442: lval = l;
0443: this .closed = closed;
0444: }
0445:
0446: RangePnt(boolean b, boolean closed) {
0447: bval = b;
0448: this .closed = closed;
0449: }
0450:
0451: RangePnt(StringScanner scanner, int type)
0452: throws IllegalArgumentException {
0453: String typeName = null;
0454: try {
0455: switch (type) {
0456: case Record.CHAR: {
0457: typeName = "character";
0458: lval = scanner.scanChar();
0459: break;
0460: }
0461: case Record.INT:
0462: case Record.LONG: {
0463: typeName = "integer";
0464: lval = scanner.scanInt();
0465: break;
0466: }
0467: case Record.FLOAT:
0468: case Record.DOUBLE: {
0469: typeName = "float";
0470: dval = scanner.scanDouble();
0471: break;
0472: }
0473: case Record.STRING: {
0474: typeName = "string";
0475: sval = scanner.scanString();
0476: break;
0477: }
0478: case Record.BOOLEAN: {
0479: typeName = "boolean";
0480: bval = scanner.scanBoolean();
0481: break;
0482: }
0483: }
0484: } catch (StringScanException e) {
0485: throw new IllegalArgumentException("Malformed "
0486: + typeName
0487: + " '"
0488: + scanner.substring(scanner.getIndex(), e
0489: .getFailIndex() + 1)
0490: + "' in range spec");
0491: }
0492: // this.closed = closed;
0493: }
0494:
0495: void setClosed(boolean closed) {
0496: this .closed = closed;
0497: }
0498:
0499: boolean getClosed() {
0500: return closed;
0501: }
0502:
0503: int compareTo(double d) {
0504: if (dval < d) {
0505: return -1;
0506: } else if (d == dval) {
0507: return 0;
0508: } else {
0509: return 1;
0510: }
0511: }
0512:
0513: int compareTo(long l) {
0514: if (lval < l) {
0515: return -1;
0516: } else if (l == lval) {
0517: return 0;
0518: } else {
0519: return 1;
0520: }
0521: }
0522:
0523: int compareTo(String s) {
0524: return sval.compareTo(s);
0525: }
0526:
0527: int compareTo(boolean b) {
0528: if (b == bval) {
0529: return 0;
0530: } else {
0531: return 1;
0532: }
0533: }
0534:
0535: public String toString() {
0536: return "{ dval=" + dval + ", lval=" + lval + ", sval="
0537: + sval + ", bval=" + bval + ", closed=" + closed
0538: + "}";
0539: }
0540: }
0541:
0542: class RangeAtom {
0543: RangePnt low = null;
0544: RangePnt high = null;
0545: RangeAtom next = null;
0546:
0547: RangeAtom(RangePnt p0, RangePnt p1, int type)
0548: throws IllegalArgumentException {
0549: int cmp = 0;
0550: switch (type) {
0551: case Record.CHAR:
0552: case Record.INT:
0553: case Record.LONG: {
0554: cmp = p0.compareTo(p1.lval);
0555: break;
0556: }
0557: case Record.FLOAT:
0558: case Record.DOUBLE: {
0559: cmp = p0.compareTo(p1.dval);
0560: break;
0561: }
0562: case Record.STRING: {
0563: cmp = p0.compareTo(p1.sval);
0564: break;
0565: }
0566: }
0567: if (cmp > 0) { // then switch high and low
0568: low = p1;
0569: high = p0;
0570: } else {
0571: low = p0;
0572: high = p1;
0573: }
0574: }
0575:
0576: RangeAtom(RangePnt p0) throws IllegalArgumentException {
0577: low = p0;
0578: }
0579:
0580: boolean match(double d) {
0581: int lc = low.compareTo(d);
0582: if (high != null) {
0583: int hc = high.compareTo(d);
0584: return (lc * hc < 0 || (low.closed && lc == 0) || (high.closed && hc == 0));
0585: } else {
0586: return lc == 0;
0587: }
0588: }
0589:
0590: boolean match(long l) {
0591: int lc = low.compareTo(l);
0592: if (high != null) {
0593: int hc = high.compareTo(l);
0594: return (lc * hc < 0 || (low.closed && lc == 0) || (high.closed && hc == 0));
0595: } else {
0596: return lc == 0;
0597: }
0598: }
0599:
0600: boolean match(String s) {
0601: int lc = low.compareTo(s);
0602: if (high != null) {
0603: int hc = high.compareTo(s);
0604: return (lc * hc < 0 || (low.closed && lc == 0) || (high.closed && hc == 0));
0605: } else {
0606: return lc == 0;
0607: }
0608: }
0609:
0610: boolean match(boolean b) {
0611: return low.compareTo(b) == 0;
0612: }
0613:
0614: public String toString() {
0615: return "low=" + (low == null ? "null" : low.toString())
0616: + ", high="
0617: + (high == null ? "null" : high.toString());
0618: }
0619: }
0620:
0621: class Record {
0622: NameDesc nameList;
0623: static final int NOTYPE = 0;
0624: static final int BOOLEAN = 1;
0625: static final int CHAR = 2;
0626: static final int INT = 3;
0627: static final int LONG = 4;
0628: static final int FLOAT = 5;
0629: static final int DOUBLE = 6;
0630: static final int STRING = 7;
0631: int type;
0632: int numValues;
0633: boolean vectorResult = false;
0634:
0635: String helpMsg = null;
0636: String valueDesc = null;
0637: String rangeDesc = null;
0638: Object resHolder = null;
0639: RangeAtom rangeList = null;
0640: RangeAtom rangeTail = null;
0641: char convertCode;
0642: boolean vval = true; // default value for now
0643:
0644: NameDesc firstNameDesc() {
0645: return nameList;
0646: }
0647:
0648: RangeAtom firstRangeAtom() {
0649: return rangeList;
0650: }
0651:
0652: int numRangeAtoms() {
0653: int cnt = 0;
0654: for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
0655: cnt++;
0656: }
0657: return cnt;
0658: }
0659:
0660: void addRangeAtom(RangeAtom ra) {
0661: if (rangeList == null) {
0662: rangeList = ra;
0663: } else {
0664: rangeTail.next = ra;
0665: }
0666: rangeTail = ra;
0667: }
0668:
0669: boolean withinRange(double d) {
0670: if (rangeList == null) {
0671: return true;
0672: }
0673: for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
0674: if (ra.match(d)) {
0675: return true;
0676: }
0677: }
0678: return false;
0679: }
0680:
0681: boolean withinRange(long l) {
0682: if (rangeList == null) {
0683: return true;
0684: }
0685: for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
0686: if (ra.match(l)) {
0687: return true;
0688: }
0689: }
0690: return false;
0691: }
0692:
0693: boolean withinRange(String s) {
0694: if (rangeList == null) {
0695: return true;
0696: }
0697: for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
0698: if (ra.match(s)) {
0699: return true;
0700: }
0701: }
0702: return false;
0703: }
0704:
0705: boolean withinRange(boolean b) {
0706: if (rangeList == null) {
0707: return true;
0708: }
0709: for (RangeAtom ra = rangeList; ra != null; ra = ra.next) {
0710: if (ra.match(b)) {
0711: return true;
0712: }
0713: }
0714: return false;
0715: }
0716:
0717: String valTypeName() {
0718: switch (convertCode) {
0719: case 'i': {
0720: return ("integer");
0721: }
0722: case 'o': {
0723: return ("octal integer");
0724: }
0725: case 'd': {
0726: return ("decimal integer");
0727: }
0728: case 'x': {
0729: return ("hex integer");
0730: }
0731: case 'c': {
0732: return ("char");
0733: }
0734: case 'b': {
0735: return ("boolean");
0736: }
0737: case 'f': {
0738: return ("float");
0739: }
0740: case 's': {
0741: return ("string");
0742: }
0743: }
0744: return ("unknown");
0745: }
0746:
0747: void scanValue(Object result, String name, String s,
0748: int resultIdx) throws ArgParseException {
0749: double dval = 0;
0750: String sval = null;
0751: long lval = 0;
0752: boolean bval = false;
0753:
0754: if (s.length() == 0) {
0755: throw new ArgParseException(name,
0756: "requires a contiguous value");
0757: }
0758: StringScanner scanner = new StringScanner(s);
0759: try {
0760: switch (convertCode) {
0761: case 'i': {
0762: lval = scanner.scanInt();
0763: break;
0764: }
0765: case 'o': {
0766: lval = scanner.scanInt(8, false);
0767: break;
0768: }
0769: case 'd': {
0770: lval = scanner.scanInt(10, false);
0771: break;
0772: }
0773: case 'x': {
0774: lval = scanner.scanInt(16, false);
0775: break;
0776: }
0777: case 'c': {
0778: lval = scanner.scanChar();
0779: break;
0780: }
0781: case 'b': {
0782: bval = scanner.scanBoolean();
0783: break;
0784: }
0785: case 'f': {
0786: dval = scanner.scanDouble();
0787: break;
0788: }
0789: case 's': {
0790: sval = scanner.getString();
0791: break;
0792: }
0793: }
0794: } catch (StringScanException e) {
0795: throw new ArgParseException(name, "malformed "
0796: + valTypeName() + " '" + s + "'");
0797: }
0798: scanner.skipWhiteSpace();
0799: if (!scanner.atEnd()) {
0800: throw new ArgParseException(name, "malformed "
0801: + valTypeName() + " '" + s + "'");
0802: }
0803: boolean outOfRange = false;
0804: switch (type) {
0805: case CHAR:
0806: case INT:
0807: case LONG: {
0808: outOfRange = !withinRange(lval);
0809: break;
0810: }
0811: case FLOAT:
0812: case DOUBLE: {
0813: outOfRange = !withinRange(dval);
0814: break;
0815: }
0816: case STRING: {
0817: outOfRange = !withinRange(sval);
0818: break;
0819: }
0820: case BOOLEAN: {
0821: outOfRange = !withinRange(bval);
0822: break;
0823: }
0824: }
0825: if (outOfRange) {
0826: String errmsg = "value " + s + " not in range ";
0827: throw new ArgParseException(name, "value '" + s
0828: + "' not in range " + rangeDesc);
0829: }
0830: if (result.getClass().isArray()) {
0831: switch (type) {
0832: case BOOLEAN: {
0833: ((boolean[]) result)[resultIdx] = bval;
0834: break;
0835: }
0836: case CHAR: {
0837: ((char[]) result)[resultIdx] = (char) lval;
0838: break;
0839: }
0840: case INT: {
0841: ((int[]) result)[resultIdx] = (int) lval;
0842: break;
0843: }
0844: case LONG: {
0845: ((long[]) result)[resultIdx] = lval;
0846: break;
0847: }
0848: case FLOAT: {
0849: ((float[]) result)[resultIdx] = (float) dval;
0850: break;
0851: }
0852: case DOUBLE: {
0853: ((double[]) result)[resultIdx] = dval;
0854: break;
0855: }
0856: case STRING: {
0857: ((String[]) result)[resultIdx] = sval;
0858: break;
0859: }
0860: }
0861: } else {
0862: switch (type) {
0863: case BOOLEAN: {
0864: ((BooleanHolder) result).value = bval;
0865: break;
0866: }
0867: case CHAR: {
0868: ((CharHolder) result).value = (char) lval;
0869: break;
0870: }
0871: case INT: {
0872: ((IntHolder) result).value = (int) lval;
0873: break;
0874: }
0875: case LONG: {
0876: ((LongHolder) result).value = lval;
0877: break;
0878: }
0879: case FLOAT: {
0880: ((FloatHolder) result).value = (float) dval;
0881: break;
0882: }
0883: case DOUBLE: {
0884: ((DoubleHolder) result).value = dval;
0885: break;
0886: }
0887: case STRING: {
0888: ((StringHolder) result).value = sval;
0889: break;
0890: }
0891: }
0892: }
0893: }
0894: }
0895:
0896: private String firstHelpOptionName() {
0897: if (firstHelpOption != null) {
0898: return firstHelpOption.nameList.name;
0899: } else {
0900: return null;
0901: }
0902: }
0903:
0904: /**
0905: * Creates an <code>ArgParser</code> with a synopsis
0906: * string, and the default help options <code>-help</code> and
0907: * <code>-?</code>.
0908: *
0909: * @param synopsisString string that briefly describes program usage,
0910: * for use by {@link #getHelpMessage getHelpMessage}.
0911: * @see ArgParser#getSynopsisString
0912: * @see ArgParser#getHelpMessage
0913: */
0914: public ArgParser(String synopsisString) {
0915: this (synopsisString, true);
0916: }
0917:
0918: /**
0919: * Creates an <code>ArgParser</code> with a synopsis
0920: * string. The help options <code>-help</code> and
0921: * <code>-?</code> are added if <code>defaultHelp</code>
0922: * is true.
0923: *
0924: * @param synopsisString string that briefly describes program usage,
0925: * for use by {@link #getHelpMessage getHelpMessage}.
0926: * @param defaultHelp if true, adds the default help options
0927: * @see ArgParser#getSynopsisString
0928: * @see ArgParser#getHelpMessage
0929: */
0930: public ArgParser(String synopsisString, boolean defaultHelp) {
0931: matchList = new Vector(128);
0932: this .synopsisString = synopsisString;
0933: if (defaultHelp) {
0934: addOption("-help,-? %h #displays help information", null);
0935: defaultHelpOption = firstHelpOption = (Record) matchList
0936: .get(0);
0937: }
0938: }
0939:
0940: /**
0941: * Returns the synopsis string used by the parser.
0942: * The synopsis string is a short description of how to invoke
0943: * the program, and usually looks something like
0944: * <p>
0945: * <prec>
0946: * "java somepackage.SomeClass [options] files ..."
0947: * </prec>
0948: *
0949: * <p> It is used in help and error messages.
0950: *
0951: * @return synopsis string
0952: * @see ArgParser#setSynopsisString
0953: * @see ArgParser#getHelpMessage
0954: */
0955: public String getSynopsisString() {
0956: return synopsisString;
0957: }
0958:
0959: /**
0960: * Sets the synopsis string used by the parser.
0961: *
0962: * @param s new synopsis string
0963: * @see ArgParser#getSynopsisString
0964: * @see ArgParser#getHelpMessage
0965: */
0966: public void setSynopsisString(String s) {
0967: synopsisString = s;
0968: }
0969:
0970: /**
0971: * Indicates whether or not help options are enabled.
0972: *
0973: * @return true if help options are enabled
0974: * @see ArgParser#setHelpOptionsEnabled
0975: * @see ArgParser#addOption
0976: */
0977: public boolean getHelpOptionsEnabled() {
0978: return helpOptionsEnabled;
0979: }
0980:
0981: /**
0982: * Enables or disables help options. Help options are those
0983: * associated with a conversion code of <code>%h</code>. If
0984: * help options are enabled, and a help option is matched,
0985: * then the string produced by
0986: * {@link #getHelpMessage getHelpMessage}
0987: * is printed to the default print stream and the program
0988: * exits with code 0. Otherwise, arguments which match help
0989: * options are ignored.
0990: *
0991: * @param enable enables help options if <code>true</code>.
0992: * @see ArgParser#getHelpOptionsEnabled
0993: * @see ArgParser#addOption
0994: * @see ArgParser#setDefaultPrintStream */
0995: public void setHelpOptionsEnabled(boolean enable) {
0996: helpOptionsEnabled = enable;
0997: }
0998:
0999: /**
1000: * Returns the default print stream used for outputting help
1001: * and error information.
1002: *
1003: * @return default print stream
1004: * @see ArgParser#setDefaultPrintStream
1005: */
1006: public PrintStream getDefaultPrintStream() {
1007: return printStream;
1008: }
1009:
1010: /**
1011: * Sets the default print stream used for outputting help
1012: * and error information.
1013: *
1014: * @param stream new default print stream
1015: * @see ArgParser#getDefaultPrintStream
1016: */
1017: public void setDefaultPrintStream(PrintStream stream) {
1018: printStream = stream;
1019: }
1020:
1021: /**
1022: * Gets the indentation used by {@link #getHelpMessage
1023: * getHelpMessage}.
1024: *
1025: * @return number of indentation columns
1026: * @see ArgParser#setHelpIndentation
1027: * @see ArgParser#getHelpMessage
1028: */
1029: public int getHelpIndentation() {
1030: return helpIndent;
1031: }
1032:
1033: /**
1034: * Sets the indentation used by {@link #getHelpMessage
1035: * getHelpMessage}. This is the number of columns that an option's help
1036: * information is indented. If the option's name and value information
1037: * can fit within this number of columns, then all information about
1038: * the option is placed on one line. Otherwise, the indented help
1039: * information is placed on a separate line.
1040: *
1041: * @param indent number of indentation columns
1042: * @see ArgParser#getHelpIndentation
1043: * @see ArgParser#getHelpMessage
1044: */
1045: public void setHelpIndentation(int indent) {
1046: helpIndent = indent;
1047: }
1048:
1049: // public void setTabSpacing (int n)
1050: // { tabSpacing = n;
1051: // }
1052:
1053: // public int getTabSpacing ()
1054: // { return tabSpacing;
1055: // }
1056:
1057: private void scanRangeSpec(Record rec, String s)
1058: throws IllegalArgumentException {
1059: StringScanner scanner = new StringScanner(s);
1060: int i0, i = 1;
1061: char c, c0, c1;
1062:
1063: scanner.setStringDelimiters(")],}");
1064: c = scanner.getc(); // swallow the first '{'
1065: scanner.skipWhiteSpace();
1066: while ((c = scanner.peekc()) != '}') {
1067: RangePnt p0, p1;
1068:
1069: if (c == '[' || c == '(') {
1070: if (rec.convertCode == 'v' || rec.convertCode == 'b') {
1071: throw new IllegalArgumentException(
1072: "Sub ranges not supported for %b or %v");
1073: }
1074: c0 = scanner.getc(); // record & swallow character
1075: scanner.skipWhiteSpace();
1076: p0 = new RangePnt(scanner, rec.type);
1077: scanner.skipWhiteSpace();
1078: if (scanner.getc() != ',') {
1079: throw new IllegalArgumentException(
1080: "Missing ',' in subrange specification");
1081: }
1082: p1 = new RangePnt(scanner, rec.type);
1083: scanner.skipWhiteSpace();
1084: if ((c1 = scanner.getc()) != ']' && c1 != ')') {
1085: throw new IllegalArgumentException(
1086: "Unterminated subrange");
1087: }
1088: if (c0 == '(') {
1089: p0.setClosed(false);
1090: }
1091: if (c1 == ')') {
1092: p1.setClosed(false);
1093: }
1094: rec.addRangeAtom(new RangeAtom(p0, p1, rec.type));
1095: } else {
1096: scanner.skipWhiteSpace();
1097: p0 = new RangePnt(scanner, rec.type);
1098: rec.addRangeAtom(new RangeAtom(p0));
1099: }
1100: scanner.skipWhiteSpace();
1101: if ((c = scanner.peekc()) == ',') {
1102: scanner.getc();
1103: scanner.skipWhiteSpace();
1104: } else if (c != '}') {
1105: throw new IllegalArgumentException(
1106: "Range spec: ',' or '}' expected");
1107: }
1108: }
1109: if (rec.numRangeAtoms() == 1) {
1110: rec.rangeDesc = s.substring(1, s.length() - 1);
1111: } else {
1112: rec.rangeDesc = s;
1113: }
1114: }
1115:
1116: private int defaultResultType(char convertCode) {
1117: switch (convertCode) {
1118: case 'i':
1119: case 'o':
1120: case 'd':
1121: case 'x': {
1122: return Record.LONG;
1123: }
1124: case 'c': {
1125: return Record.CHAR;
1126: }
1127: case 'v':
1128: case 'b': {
1129: return Record.BOOLEAN;
1130: }
1131: case 'f': {
1132: return Record.DOUBLE;
1133: }
1134: case 's': {
1135: return Record.STRING;
1136: }
1137: }
1138: return Record.NOTYPE;
1139: }
1140:
1141: /**
1142: * Adds a new option description to the parser. The method takes two
1143: * arguments: a specification string, and a result holder in which to
1144: * store the associated value.
1145: *
1146: * <p>The specification string has the general form
1147: *
1148: * <p> <var>optionNames</var>
1149: * <code>%</code><var>conversionCode</var>
1150: * [<code>{</code><var>rangeSpec</var><code>}</code>]
1151: * [<code>X</code><var>multiplier</var>]
1152: * [<code>#</code><var>valueDescription</var>]
1153: * [<code>#</code><var>optionDescription</var>] </code>
1154: *
1155: * <p>
1156: * where
1157: * <ul> <p><li><var>optionNames</var> is a
1158: * comma-separated list of names for the option
1159: * (such as <code>-f, --file</code>).
1160: *
1161: * <p><li><var>conversionCode</var> is a single letter,
1162: * following a <code>%</code> character, specifying
1163: * information about what value the option requires:
1164: *
1165: * <table>
1166: * <tr><td><code>%f</code></td><td>a floating point number</td>
1167: * <tr><td><code>%i</code></td><td>an integer, in either decimal,
1168: * hex (if preceeded by <code>0x</code>), or
1169: * octal (if preceeded by <code>0</code>)</td>
1170: * <tr valign=top>
1171: * <td><code>%d</code></td><td>a decimal integer</td>
1172: * <tr valign=top>
1173: * <td><code>%o</code></td><td>an octal integer</td>
1174: * <tr valign=top>
1175: * <td><code>%h</code></td><td>a hex integer (without the
1176: * preceeding <code>0x</code>)</td>
1177: * <tr valign=top>
1178: * <td><code>%c</code></td><td>a single character, including
1179: * escape sequences (such as <code>\n</code> or <code>\007</code>),
1180: * and optionally enclosed in single quotes
1181: * <tr valign=top>
1182: * <td><code>%b</code></td><td>a boolean value (<code>true</code>
1183: * or <code>false</code>)</td>
1184: * <tr valign=top>
1185: * <td><code>%s</code></td><td>a string. This will
1186: * be the argument string itself (or its remainder, in
1187: * the case of a single word option)</td>
1188: * <tr valign=top>
1189: * <td><code>%v</code></td><td>no explicit value is expected,
1190: * but a boolean value of <code>true</code> (by default)
1191: * will be stored into the associated result holder if this
1192: * option is matched. If one wishes to have a value of
1193: * <code>false</code> stored instead, then the <code>%v</code>
1194: * should be followed by a "range spec" containing
1195: * <code>false</code>, as in <code>%v{false}</code>.
1196: * </table>
1197: *
1198: * <p><li><var>rangeSpec</var> is an optional range specification,
1199: * placed inside curly braces, consisting of a
1200: * comma-separated list of range items each specifying
1201: * permissible values for the option. A range item may be an
1202: * individual value, or it may itself be a subrange,
1203: * consisting of two individual values, separated by a comma,
1204: * and enclosed in square or round brackets. Square and round
1205: * brackets denote closed and open endpoints of a subrange, indicating
1206: * that the associated endpoint value is included or excluded
1207: * from the subrange.
1208: * The values specified in the range spec need to be
1209: * consistent with the type of value expected by the option.
1210: *
1211: * <p><b>Examples:</b>
1212: *
1213: * <p>A range spec of <code>{2,4,8,16}</code> for an integer
1214: * value will allow the integers 2, 4, 8, or 16.
1215: *
1216: * <p>A range spec of <code>{[-1.0,1.0]}</code> for a floating
1217: * point value will allow any floating point number in the
1218: * range -1.0 to 1.0.
1219: *
1220: * <p>A range spec of <code>{(-88,100],1000}</code> for an integer
1221: * value will allow values > -88 and <= 100, as well as 1000.
1222: *
1223: * <p>A range spec of <code>{"foo", "bar", ["aaa","zzz")} </code> for a
1224: * string value will allow strings equal to <code>"foo"</code> or
1225: * <code>"bar"</code>, plus any string lexically greater than or equal
1226: * to <code>"aaa"</code> but less then <code>"zzz"</code>.
1227: *
1228: * <p><li><var>multiplier</var> is an optional integer,
1229: * following a <code>X</code> character,
1230: * indicating the number of values which the option expects.
1231: * If the multiplier is not specified, it is assumed to be
1232: * 1. If the multiplier value is greater than 1, then the
1233: * result holder should be either an array (of appropriate
1234: * type) with a length greater than or equal to the multiplier
1235: * value, or a <code>java.util.Vector</code>
1236: * <a href=#vectorHolder>as discussed below</a>.
1237: *
1238: * <p><li><var>valueDescription</var> is an optional
1239: * description of the option's value requirements,
1240: * and consists of all
1241: * characters between two <code>#</code> characters.
1242: * The final <code>#</code> character initiates the
1243: * <i>option description</i>, which may be empty.
1244: * The value description is used in
1245: * <a href=#helpInfo>generating help messages</a>.
1246: *
1247: * <p><li><var>optionDescription</var> is an optional
1248: * description of the option itself, consisting of all
1249: * characters between a <code>#</code> character
1250: * and the end of the specification string.
1251: * The option description is used in
1252: * <a href=#helpInfo>generating help messages</a>.
1253: * </ul>
1254: *
1255: * <p>The result holder must be an object capable of holding
1256: * a value compatible with the conversion code,
1257: * or it must be a <code>java.util.Vector</code>.
1258: * When the option is matched, its associated value is
1259: * placed in the result holder. If the same option is
1260: * matched repeatedly, the result holder value will be overwritten,
1261: * unless the result holder is a <code>java.util.Vector</code>,
1262: * in which
1263: * case new holder objects for each match will be allocated
1264: * and added to the vector. Thus if
1265: * multiple instances of an option are desired by the
1266: * program, the result holder should be a
1267: * <code>java.util.Vector</code>.
1268: *
1269: * <p>If the result holder is not a <code>Vector</code>, then
1270: * it must correspond as follows to the conversion code:
1271: *
1272: * <table>
1273: * <tr valign=top>
1274: * <td><code>%i</code>, <code>%d</code>, <code>%x</code>,
1275: * <code>%o</code></td>
1276: * <td>{@link argparser.IntHolder IntHolder},
1277: * {@link argparser.LongHolder LongHolder}, <code>int[]</code>, or
1278: * <code>long[]</code></td>
1279: * </tr>
1280: *
1281: * <tr valign=top>
1282: * <td><code>%f</code></td>
1283: * <td>{@link argparser.FloatHolder FloatHolder},
1284: * {@link argparser.DoubleHolder DoubleHolder},
1285: * <code>float[]</code>, or
1286: * <code>double[]</code></td>
1287: * </tr>
1288: *
1289: * <tr valign=top>
1290: * <td><code>%b</code>, <code>%v</code></td>
1291: * <td>{@link argparser.BooleanHolder BooleanHolder} or
1292: * <code>boolean[]</code></td>
1293: * </tr>
1294: *
1295: * <tr valign=top>
1296: * <td><code>%s</code></td>
1297: * <td>{@link argparser.StringHolder StringHolder} or
1298: * <code>String[]</code></td>
1299: * </tr>
1300: *
1301: * <tr valign=top>
1302: * <td><code>%c</code></td>
1303: * <td>{@link argparser.CharHolder CharHolder} or
1304: * <code>char[]</code></td>
1305: * </tr>
1306: * </table>
1307: *
1308: * <p>In addition, if the multiplier is greater than 1,
1309: * then only the array type indicated above may be used,
1310: * and the array must be at least as long as the multiplier.
1311: *
1312: * <p><a name=vectorHolder>If the result holder is a
1313: * <code>Vector</code>, then the system will create an appropriate
1314: * result holder object and add it to the vector. Multiple occurances
1315: * of the option will cause multiple results to be added to the vector.
1316: *
1317: * <p>The object allocated by the system to store the result
1318: * will correspond to the conversion code as follows:
1319: *
1320: * <table>
1321: * <tr valign=top>
1322: * <td><code>%i</code>, <code>%d</code>, <code>%x</code>,
1323: * <code>%o</code></td>
1324: * <td>{@link argparser.LongHolder LongHolder}, or
1325: * <code>long[]</code> if the multiplier value exceeds 1</td>
1326: * </tr>
1327: *
1328: * <tr valign=top>
1329: * <td><code>%f</code></td>
1330: * <td>{@link argparser.DoubleHolder DoubleHolder}, or
1331: * <code>double[]</code> if the multiplier value exceeds 1</td>
1332: * </tr>
1333: *
1334: * <tr valign=top>
1335: * <td><code>%b</code>, <code>%v</code></td>
1336: * <td>{@link argparser.BooleanHolder BooleanHolder}, or
1337: * <code>boolean[]</code>
1338: * if the multiplier value exceeds 1</td>
1339: * </tr>
1340: *
1341: * <tr valign=top>
1342: * <td><code>%s</code></td>
1343: * <td>{@link argparser.StringHolder StringHolder}, or
1344: * <code>String[]</code>
1345: * if the multiplier value exceeds 1</td>
1346: * </tr>
1347: *
1348: * <tr valign=top>
1349: * <td><code>%c</code></td>
1350: * <td>{@link argparser.CharHolder CharHolder}, or <code>char[]</code>
1351: * if the multiplier value exceeds 1</td>
1352: * </tr>
1353: * </table>
1354: *
1355: * @param spec the specification string
1356: * @param resHolder object in which to store the associated
1357: * value
1358: * @throws IllegalArgumentException if there is an error in
1359: * the specification or if the result holder is of an invalid
1360: * type. */
1361: public void addOption(String spec, Object resHolder)
1362: throws IllegalArgumentException {
1363: // null terminated string is easier to parse
1364: StringScanner scanner = new StringScanner(spec);
1365: Record rec = null;
1366: NameDesc nameTail = null;
1367: NameDesc ndesc;
1368: int i0, i1;
1369: char c;
1370:
1371: do {
1372: ndesc = new NameDesc();
1373: boolean nameEndsInWhiteSpace = false;
1374:
1375: scanner.skipWhiteSpace();
1376: i0 = scanner.getIndex();
1377: while (!Character.isWhitespace(c = scanner.getc())
1378: && c != ',' && c != '%' && c != '\000')
1379: ;
1380: i1 = scanner.getIndex();
1381: if (c != '\000') {
1382: i1--;
1383: }
1384: if (i0 == i1) { // then c is one of ',' '%' or '\000'
1385: throw new IllegalArgumentException(
1386: "Null option name given");
1387: }
1388: if (Character.isWhitespace(c)) {
1389: nameEndsInWhiteSpace = true;
1390: scanner.skipWhiteSpace();
1391: c = scanner.getc();
1392: }
1393: if (c == '\000') {
1394: throw new IllegalArgumentException(
1395: "No conversion character given");
1396: }
1397: if (c != ',' && c != '%') {
1398: throw new IllegalArgumentException(
1399: "Names not separated by ','");
1400: }
1401: ndesc.name = scanner.substring(i0, i1);
1402: if (rec == null) {
1403: rec = new Record();
1404: rec.nameList = ndesc;
1405: } else {
1406: nameTail.next = ndesc;
1407: }
1408: nameTail = ndesc;
1409: ndesc.oneWord = !nameEndsInWhiteSpace;
1410: } while (c != '%');
1411:
1412: if (nameTail == null) {
1413: throw new IllegalArgumentException("Null option name given");
1414: }
1415: if (!nameTail.oneWord) {
1416: for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
1417: ndesc.oneWord = false;
1418: }
1419: }
1420: c = scanner.getc();
1421: if (c == '\000') {
1422: throw new IllegalArgumentException(
1423: "No conversion character given");
1424: }
1425: if (validConversionCodes.indexOf(c) == -1) {
1426: throw new IllegalArgumentException("Conversion code '" + c
1427: + "' not one of '" + validConversionCodes + "'");
1428: }
1429: rec.convertCode = c;
1430:
1431: if (resHolder instanceof Vector) {
1432: rec.vectorResult = true;
1433: rec.type = defaultResultType(rec.convertCode);
1434: } else {
1435: switch (rec.convertCode) {
1436: case 'i':
1437: case 'o':
1438: case 'd':
1439: case 'x': {
1440: if (resHolder instanceof LongHolder
1441: || resHolder instanceof long[]) {
1442: rec.type = Record.LONG;
1443: } else if (resHolder instanceof IntHolder
1444: || resHolder instanceof int[]) {
1445: rec.type = Record.INT;
1446: } else {
1447: throw new IllegalArgumentException(
1448: "Invalid result holder for %" + c);
1449: }
1450: break;
1451: }
1452: case 'c': {
1453: if (!(resHolder instanceof CharHolder)
1454: && !(resHolder instanceof char[])) {
1455: throw new IllegalArgumentException(
1456: "Invalid result holder for %c");
1457: }
1458: rec.type = Record.CHAR;
1459: break;
1460: }
1461: case 'v':
1462: case 'b': {
1463: if (!(resHolder instanceof BooleanHolder)
1464: && !(resHolder instanceof boolean[])) {
1465: throw new IllegalArgumentException(
1466: "Invalid result holder for %" + c);
1467: }
1468: rec.type = Record.BOOLEAN;
1469: break;
1470: }
1471: case 'f': {
1472: if (resHolder instanceof DoubleHolder
1473: || resHolder instanceof double[]) {
1474: rec.type = Record.DOUBLE;
1475: } else if (resHolder instanceof FloatHolder
1476: || resHolder instanceof float[]) {
1477: rec.type = Record.FLOAT;
1478: } else {
1479: throw new IllegalArgumentException(
1480: "Invalid result holder for %f");
1481: }
1482: break;
1483: }
1484: case 's': {
1485: if (!(resHolder instanceof StringHolder)
1486: && !(resHolder instanceof String[])) {
1487: throw new IllegalArgumentException(
1488: "Invalid result holder for %s");
1489: }
1490: rec.type = Record.STRING;
1491: break;
1492: }
1493: case 'h': { // resHolder is ignored for this type
1494: break;
1495: }
1496: }
1497: }
1498: if (rec.convertCode == 'h') {
1499: rec.resHolder = null;
1500: } else {
1501: rec.resHolder = resHolder;
1502: }
1503:
1504: scanner.skipWhiteSpace();
1505: // get the range specification, if any
1506: if (scanner.peekc() == '{') {
1507: if (rec.convertCode == 'h') {
1508: throw new IllegalArgumentException(
1509: "Ranges not supported for %h");
1510: }
1511: // int bcnt = 0;
1512: i0 = scanner.getIndex(); // beginning of range spec
1513: do {
1514: c = scanner.getc();
1515: if (c == '\000') {
1516: throw new IllegalArgumentException(
1517: "Unterminated range specification");
1518: }
1519: // else if (c=='[' || c=='(')
1520: // { bcnt++;
1521: // }
1522: // else if (c==']' || c==')')
1523: // { bcnt--;
1524: // }
1525: // if ((rec.convertCode=='v'||rec.convertCode=='b') && bcnt>1)
1526: // { throw new IllegalArgumentException
1527: // ("Sub ranges not supported for %b or %v");
1528: // }
1529: } while (c != '}');
1530: // if (c != ']')
1531: // { throw new IllegalArgumentException
1532: // ("Range specification must end with ']'");
1533: // }
1534: i1 = scanner.getIndex(); // end of range spec
1535: scanRangeSpec(rec, scanner.substring(i0, i1));
1536: if (rec.convertCode == 'v' && rec.rangeList != null) {
1537: rec.vval = rec.rangeList.low.bval;
1538: }
1539: }
1540: // check for value multiplicity information, if any
1541: if (scanner.peekc() == 'X') {
1542: if (rec.convertCode == 'h') {
1543: throw new IllegalArgumentException(
1544: "Multipliers not supported for %h");
1545: }
1546: scanner.getc();
1547: try {
1548: rec.numValues = (int) scanner.scanInt();
1549: } catch (StringScanException e) {
1550: throw new IllegalArgumentException(
1551: "Malformed value multiplier");
1552: }
1553: if (rec.numValues <= 0) {
1554: throw new IllegalArgumentException(
1555: "Value multiplier number must be > 0");
1556: }
1557: } else {
1558: rec.numValues = 1;
1559: }
1560: if (rec.numValues > 1) {
1561: for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
1562: if (ndesc.oneWord) {
1563: throw new IllegalArgumentException(
1564: "Multiplier value incompatible with one word option "
1565: + ndesc.name);
1566: }
1567: }
1568: }
1569: if (resHolder != null && resHolder.getClass().isArray()) {
1570: if (Array.getLength(resHolder) < rec.numValues) {
1571: throw new IllegalArgumentException(
1572: "Result holder array must have a length >= "
1573: + rec.numValues);
1574: }
1575: } else {
1576: if (rec.numValues > 1 && !(resHolder instanceof Vector)) {
1577: throw new IllegalArgumentException(
1578: "Multiplier requires result holder to be an array of length >= "
1579: + rec.numValues);
1580: }
1581: }
1582:
1583: // skip white space following conversion information
1584: scanner.skipWhiteSpace();
1585:
1586: // get the help message, if any
1587:
1588: if (!scanner.atEnd()) {
1589: if (scanner.getc() != '#') {
1590: throw new IllegalArgumentException(
1591: "Illegal character(s), expecting '#'");
1592: }
1593: String helpInfo = scanner.substring(scanner.getIndex());
1594: // look for second '#'. If there is one, then info
1595: // between the first and second '#' is the value descriptor.
1596: int k = helpInfo.indexOf("#");
1597: if (k != -1) {
1598: rec.valueDesc = helpInfo.substring(0, k);
1599: rec.helpMsg = helpInfo.substring(k + 1);
1600: } else {
1601: rec.helpMsg = helpInfo;
1602: }
1603: } else {
1604: rec.helpMsg = "";
1605: }
1606: // add option information to match list
1607: if (rec.convertCode == 'h'
1608: && firstHelpOption == defaultHelpOption) {
1609: matchList.remove(defaultHelpOption);
1610: firstHelpOption = rec;
1611: }
1612: matchList.add(rec);
1613: }
1614:
1615: Record lastMatchRecord() {
1616: return (Record) matchList.lastElement();
1617: }
1618:
1619: private Record getRecord(String arg, ObjectHolder ndescHolder) {
1620: NameDesc ndesc;
1621: for (int i = 0; i < matchList.size(); i++) {
1622: Record rec = (Record) matchList.get(i);
1623: for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
1624: if (rec.convertCode != 'v' && ndesc.oneWord) {
1625: if (arg.startsWith(ndesc.name)) {
1626: if (ndescHolder != null) {
1627: ndescHolder.value = ndesc;
1628: }
1629: return rec;
1630: }
1631: } else {
1632: if (arg.equals(ndesc.name)) {
1633: if (ndescHolder != null) {
1634: ndescHolder.value = ndesc;
1635: }
1636: return rec;
1637: }
1638: }
1639: }
1640: }
1641: return null;
1642: }
1643:
1644: Object getResultHolder(String arg) {
1645: Record rec = getRecord(arg, null);
1646: return (rec != null) ? rec.resHolder : null;
1647: }
1648:
1649: String getOptionName(String arg) {
1650: ObjectHolder ndescHolder = new ObjectHolder();
1651: Record rec = getRecord(arg, ndescHolder);
1652: return (rec != null) ? ((NameDesc) ndescHolder.value).name
1653: : null;
1654: }
1655:
1656: String getOptionRangeDesc(String arg) {
1657: Record rec = getRecord(arg, null);
1658: return (rec != null) ? rec.rangeDesc : null;
1659: }
1660:
1661: String getOptionTypeName(String arg) {
1662: Record rec = getRecord(arg, null);
1663: return (rec != null) ? rec.valTypeName() : null;
1664: }
1665:
1666: private Object createResultHolder(Record rec) {
1667: if (rec.numValues == 1) {
1668: switch (rec.type) {
1669: case Record.LONG: {
1670: return new LongHolder();
1671: }
1672: case Record.CHAR: {
1673: return new CharHolder();
1674: }
1675: case Record.BOOLEAN: {
1676: return new BooleanHolder();
1677: }
1678: case Record.DOUBLE: {
1679: return new DoubleHolder();
1680: }
1681: case Record.STRING: {
1682: return new StringHolder();
1683: }
1684: }
1685: } else {
1686: switch (rec.type) {
1687: case Record.LONG: {
1688: return new long[rec.numValues];
1689: }
1690: case Record.CHAR: {
1691: return new char[rec.numValues];
1692: }
1693: case Record.BOOLEAN: {
1694: return new boolean[rec.numValues];
1695: }
1696: case Record.DOUBLE: {
1697: return new double[rec.numValues];
1698: }
1699: case Record.STRING: {
1700: return new String[rec.numValues];
1701: }
1702: }
1703: }
1704: return null; // can't happen
1705: }
1706:
1707: static void stringToArgs(Vector vec, String s,
1708: boolean allowQuotedStrings) throws StringScanException {
1709: StringScanner scanner = new StringScanner(s);
1710: scanner.skipWhiteSpace();
1711: while (!scanner.atEnd()) {
1712: if (allowQuotedStrings) {
1713: vec.add(scanner.scanString());
1714: } else {
1715: vec.add(scanner.scanNonWhiteSpaceString());
1716: }
1717: scanner.skipWhiteSpace();
1718: }
1719: }
1720:
1721: /**
1722: * Reads in a set of strings from a reader and prepends them to an
1723: * argument list. Strings are delimited by either whitespace or
1724: * double quotes <code>"</code>. The character <code>#</code> acts as
1725: * a comment character, causing input to the end of the current line to
1726: * be ignored.
1727: *
1728: * @param reader Reader from which to read the strings
1729: * @param args Initial set of argument values. Can be
1730: * specified as <code>null</code>.
1731: * @throws IOException if an error occured while reading.
1732: */
1733: public static String[] prependArgs(Reader reader, String[] args)
1734: throws IOException {
1735: if (args == null) {
1736: args = new String[0];
1737: }
1738: LineNumberReader lineReader = new LineNumberReader(reader);
1739: Vector vec = new Vector(100, 100);
1740: String line;
1741: int i, k;
1742:
1743: while ((line = lineReader.readLine()) != null) {
1744: int commentIdx = line.indexOf("#");
1745: if (commentIdx != -1) {
1746: line = line.substring(0, commentIdx);
1747: }
1748: try {
1749: stringToArgs(vec, line, /*allowQuotedStings=*/true);
1750: } catch (StringScanException e) {
1751: throw new IOException("malformed string, line "
1752: + lineReader.getLineNumber());
1753: }
1754: }
1755: String[] result = new String[vec.size() + args.length];
1756: for (i = 0; i < vec.size(); i++) {
1757: result[i] = (String) vec.get(i);
1758: }
1759: for (k = 0; k < args.length; k++) {
1760: result[i++] = args[k];
1761: }
1762: return result;
1763: }
1764:
1765: /**
1766: * Reads in a set of strings from a file and prepends them to an
1767: * argument list. Strings are delimited by either whitespace or double
1768: * quotes <code>"</code>. The character <code>#</code> acts as a
1769: * comment character, causing input to the end of the current line to
1770: * be ignored.
1771: *
1772: * @param file File to be read
1773: * @param args Initial set of argument values. Can be
1774: * specified as <code>null</code>.
1775: * @throws IOException if an error occured while reading the file.
1776: */
1777: public static String[] prependArgs(File file, String[] args)
1778: throws IOException {
1779: if (args == null) {
1780: args = new String[0];
1781: }
1782: if (!file.canRead()) {
1783: return args;
1784: }
1785: try {
1786: return prependArgs(new FileReader(file), args);
1787: } catch (IOException e) {
1788: throw new IOException("File " + file.getName() + ": "
1789: + e.getMessage());
1790: }
1791: }
1792:
1793: /**
1794: * Sets the parser's error message.
1795: *
1796: * @param s Error message
1797: */
1798: protected void setError(String msg) {
1799: errMsg = msg;
1800: }
1801:
1802: /**
1803: * Prints an error message, along with a pointer to help options,
1804: * if available, and causes the program to exit with code 1.
1805: */
1806: public void printErrorAndExit(String msg) {
1807: if (helpOptionsEnabled && firstHelpOptionName() != null) {
1808: msg += "\nUse " + firstHelpOptionName()
1809: + " for help information";
1810: }
1811: if (printStream != null) {
1812: printStream.println(msg);
1813: }
1814: System.exit(1);
1815: }
1816:
1817: /**
1818: * Matches arguments within an argument list.
1819: *
1820: * <p>In the event of an erroneous or unmatched argument, the method
1821: * prints a message and exits the program with code 1.
1822: *
1823: * <p>If help options are enabled and one of the arguments matches a
1824: * help option, then the result of {@link #getHelpMessage
1825: * getHelpMessage} is printed to the default print stream and the
1826: * program exits with code 0. If help options are not enabled, they
1827: * are ignored.
1828: *
1829: * @param args argument list
1830: * @see ArgParser#getDefaultPrintStream
1831: */
1832: public void matchAllArgs(String[] args) {
1833: matchAllArgs(args, 0, EXIT_ON_UNMATCHED | EXIT_ON_ERROR);
1834: }
1835:
1836: /**
1837: * Matches arguments within an argument list and returns
1838: * those which were not matched. The matching starts at a location
1839: * in <code>args</code> specified by <code>idx</code>, and
1840: * unmatched arguments are returned in a String array.
1841: *
1842: * <p>In the event of an erroneous argument, the method either prints a
1843: * message and exits the program (if {@link #EXIT_ON_ERROR} is
1844: * set in <code>exitFlags</code>)
1845: * or terminates the matching and creates a error message that
1846: * can be retrieved by {@link #getErrorMessage}.
1847: *
1848: * <p>In the event of an umatched argument, the method will print a
1849: * message and exit if {@link #EXIT_ON_UNMATCHED} is set
1850: * in <code>errorFlags</code>.
1851: * Otherwise, the unmatched argument will be appended to the returned
1852: * array of unmatched values, and the matching will continue at the
1853: * next location.
1854: *
1855: * <p>If help options are enabled and one of the arguments matches a
1856: * help option, then the result of {@link #getHelpMessage
1857: * getHelpMessage} is printed to the the default print stream and the
1858: * program exits with code 0. If help options are not enabled, then
1859: * they will not be matched.
1860: *
1861: * @param args argument list
1862: * @param idx starting location in list
1863: * @param exitFlags conditions causing the program to exit. Should be
1864: * an or-ed combintion of {@link #EXIT_ON_ERROR} or {@link
1865: * #EXIT_ON_UNMATCHED}.
1866: * @return array of arguments that were not matched, or
1867: * <code>null</code> if all arguments were successfully matched
1868: * @see ArgParser#getErrorMessage
1869: * @see ArgParser#getDefaultPrintStream
1870: */
1871: public String[] matchAllArgs(String[] args, int idx, int exitFlags) {
1872: Vector unmatched = new Vector(10);
1873:
1874: while (idx < args.length) {
1875: try {
1876: idx = matchArg(args, idx);
1877: if (unmatchedArg != null) {
1878: if ((exitFlags & EXIT_ON_UNMATCHED) != 0) {
1879: printErrorAndExit("Unrecognized argument: "
1880: + unmatchedArg);
1881: } else {
1882: unmatched.add(unmatchedArg);
1883: }
1884: }
1885: } catch (ArgParseException e) {
1886: if ((exitFlags & EXIT_ON_ERROR) != 0) {
1887: printErrorAndExit(e.getMessage());
1888: }
1889: break;
1890: }
1891: }
1892: if (unmatched.size() == 0) {
1893: return null;
1894: } else {
1895: return (String[]) unmatched.toArray(new String[0]);
1896: }
1897: }
1898:
1899: /**
1900: * Matches one option starting at a specified location in an argument
1901: * list. The method returns the location in the list where the next
1902: * match should begin.
1903: *
1904: * <p>In the event of an erroneous argument, the method throws
1905: * an {@link argparser.ArgParseException ArgParseException}
1906: * with an appropriate error message. This error
1907: * message can also be retrieved using
1908: * {@link #getErrorMessage getErrorMessage}.
1909: *
1910: * <p>In the event of an umatched argument, the method will return idx
1911: * + 1, and {@link #getUnmatchedArgument getUnmatchedArgument} will
1912: * return a copy of the unmatched argument. If an argument is matched,
1913: * {@link #getUnmatchedArgument getUnmatchedArgument} will return
1914: * <code>null</code>.
1915: *
1916: * <p>If help options are enabled and the argument matches a help
1917: * option, then the result of {@link #getHelpMessage getHelpMessage} is printed to
1918: * the the default print stream and the program exits with code 0. If
1919: * help options are not enabled, then they are ignored.
1920: *
1921: * @param args argument list
1922: * @param idx location in list where match should start
1923: * @return location in list where next match should start
1924: * @throws ArgParseException if there was an error performing
1925: * the match (such as improper or insufficient values).
1926: * @see ArgParser#setDefaultPrintStream
1927: * @see ArgParser#getHelpOptionsEnabled
1928: * @see ArgParser#getErrorMessage
1929: * @see ArgParser#getUnmatchedArgument
1930: */
1931: public int matchArg(String[] args, int idx)
1932: throws ArgParseException {
1933: unmatchedArg = null;
1934: setError(null);
1935: try {
1936: ObjectHolder ndescHolder = new ObjectHolder();
1937: Record rec = getRecord(args[idx], ndescHolder);
1938: if (rec == null
1939: || (rec.convertCode == 'h' && !helpOptionsEnabled)) { // didn't match
1940: unmatchedArg = new String(args[idx]);
1941: return idx + 1;
1942: }
1943: NameDesc ndesc = (NameDesc) ndescHolder.value;
1944: Object result;
1945: if (rec.resHolder instanceof Vector) {
1946: result = createResultHolder(rec);
1947: } else {
1948: result = rec.resHolder;
1949: }
1950: if (rec.convertCode == 'h') {
1951: if (helpOptionsEnabled) {
1952: printStream.println(getHelpMessage());
1953: System.exit(0);
1954: } else {
1955: return idx + 1;
1956: }
1957: } else if (rec.convertCode != 'v') {
1958: if (ndesc.oneWord) {
1959: rec.scanValue(result, ndesc.name, args[idx]
1960: .substring(ndesc.name.length()), 0);
1961: } else {
1962: if (idx + rec.numValues >= args.length) {
1963: throw new ArgParseException(
1964: ndesc.name,
1965: "requires "
1966: + rec.numValues
1967: + " value"
1968: + (rec.numValues > 1 ? "s" : ""));
1969: }
1970: for (int k = 0; k < rec.numValues; k++) {
1971: rec.scanValue(result, ndesc.name, args[++idx],
1972: k);
1973: }
1974: }
1975: } else {
1976: if (rec.resHolder instanceof BooleanHolder) {
1977: ((BooleanHolder) result).value = rec.vval;
1978: } else {
1979: for (int k = 0; k < rec.numValues; k++) {
1980: ((boolean[]) result)[k] = rec.vval;
1981: }
1982: }
1983: }
1984: if (rec.resHolder instanceof Vector) {
1985: ((Vector) rec.resHolder).add(result);
1986: }
1987: } catch (ArgParseException e) {
1988: setError(e.getMessage());
1989: throw e;
1990: }
1991: return idx + 1;
1992: }
1993:
1994: private String spaceString(int n) {
1995: StringBuffer sbuf = new StringBuffer(n);
1996: for (int i = 0; i < n; i++) {
1997: sbuf.append(' ');
1998: }
1999: return sbuf.toString();
2000: }
2001:
2002: // public String getShortHelpMessage ()
2003: // {
2004: // String s;
2005: // Record rec;
2006: // NameDesc ndesc;
2007: // int initialIndent = 8;
2008: // int col = initialIndent;
2009:
2010: // if (maxcols <= 0)
2011: // { maxcols = 80;
2012: // }
2013: // if (matchList.size() > 0)
2014: // { ps.print (spaceString(initialIndent));
2015: // }
2016: // for (int i=0; i<matchList.size(); i++)
2017: // { rec = (Record)matchList.get(i);
2018: // s = "[";
2019: // for (ndesc=rec.nameList; ndesc!=null; ndesc=ndesc.next)
2020: // { s = s + ndesc.name;
2021: // if (ndesc.oneWord == false)
2022: // { s = s + " ";
2023: // }
2024: // if (ndesc.next != null)
2025: // { s = s + ",";
2026: // }
2027: // }
2028: // if (rec.convertCode != 'v' && rec.convertCode != 'h')
2029: // { if (rec.valueDesc != null)
2030: // { s += rec.valueDesc;
2031: // }
2032: // else
2033: // { s = s + "<" + rec.valTypeName() + ">";
2034: // if (rec.numValues > 1)
2035: // { s += "X" + rec.numValues;
2036: // }
2037: // }
2038: // }
2039: // s = s + "]";
2040: // /*
2041: // (col+=s.length()) > (maxcols-1) => we will spill over edge.
2042: // we use (maxcols-1) because if we go right to the edge
2043: // (maxcols), we get wrap new line inserted "for us".
2044: // i != 0 means we print the first entry, no matter
2045: // how long it is. Subsequent entries are printed
2046: // full length anyway. */
2047:
2048: // if ((col+=s.length()) > (maxcols-1) && i != 0)
2049: // { col = initialIndent+s.length();
2050: // ps.print ("\n" + spaceString(initialIndent));
2051: // }
2052: // ps.print (s);
2053: // }
2054: // if (matchList.size() > 0)
2055: // { ps.print ('\n');
2056: // ps.flush();
2057: // }
2058: // }
2059:
2060: /**
2061: * Returns a string describing the allowed options
2062: * in detail.
2063: *
2064: * @return help information string.
2065: */
2066: public String getHelpMessage() {
2067: Record rec;
2068: NameDesc ndesc;
2069: boolean hasOneWordAlias = false;
2070: String s;
2071:
2072: s = "Usage: " + synopsisString + "\n";
2073: s += "Options include:\n\n";
2074: for (int i = 0; i < matchList.size(); i++) {
2075: String optionInfo = "";
2076: rec = (Record) matchList.get(i);
2077: if (rec.convertCode == 'h' && !helpOptionsEnabled) {
2078: continue;
2079: }
2080: for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
2081: if (ndesc.oneWord) {
2082: hasOneWordAlias = true;
2083: break;
2084: }
2085: }
2086: for (ndesc = rec.nameList; ndesc != null; ndesc = ndesc.next) {
2087: optionInfo += ndesc.name;
2088: if (hasOneWordAlias && !ndesc.oneWord) {
2089: optionInfo += " ";
2090: }
2091: if (ndesc.next != null) {
2092: optionInfo += ",";
2093: }
2094: }
2095: if (!hasOneWordAlias) {
2096: optionInfo += " ";
2097: }
2098: if (rec.convertCode != 'v' && rec.convertCode != 'h') {
2099: if (rec.valueDesc != null) {
2100: optionInfo += rec.valueDesc;
2101: } else {
2102: if (rec.rangeDesc != null) {
2103: optionInfo += "<" + rec.valTypeName() + " "
2104: + rec.rangeDesc + ">";
2105: } else {
2106: optionInfo += "<" + rec.valTypeName() + ">";
2107: }
2108: }
2109: }
2110: if (rec.numValues > 1) {
2111: optionInfo += "X" + rec.numValues;
2112: }
2113: s += optionInfo;
2114: if (rec.helpMsg.length() > 0) {
2115: int pad = helpIndent - optionInfo.length();
2116: if (pad < 2) {
2117: s += '\n';
2118: pad = helpIndent;
2119: }
2120: s += spaceString(pad) + rec.helpMsg;
2121: }
2122: s += '\n';
2123: }
2124: return s;
2125: }
2126:
2127: /**
2128: * Returns the parser's error message. This is automatically
2129: * set whenever an error is encountered in <code>matchArg</code>
2130: * or <code>matchAllArgs</code>, and is automatically set to
2131: * <code>null</code> at the beginning of these methods.
2132: *
2133: * @return error message
2134: */
2135: public String getErrorMessage() {
2136: return errMsg;
2137: }
2138:
2139: /**
2140: * Returns the value of an unmatched argument discovered {@link
2141: * #matchArg matchArg} or {@link #matchAllArgs(String[],int,int)
2142: * matchAllArgs}. If there was no unmatched argument,
2143: * <code>null</code> is returned.
2144: *
2145: * @return unmatched argument
2146: */
2147: public String getUnmatchedArgument() {
2148: return unmatchedArg;
2149: }
2150: }
|