0001: /*
0002: * Copyright (c) 2001 by Matt Welsh and The Regents of the University of
0003: * California. All rights reserved.
0004: *
0005: * Permission to use, copy, modify, and distribute this software and its
0006: * documentation for any purpose, without fee, and without written agreement is
0007: * hereby granted, provided that the above copyright notice and the following
0008: * two paragraphs appear in all copies of this software.
0009: *
0010: * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
0011: * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
0012: * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
0013: * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0014: *
0015: * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
0016: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
0017: * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
0018: * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
0019: * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
0020: *
0021: * Author: Matt Welsh <mdw@cs.berkeley.edu>
0022: *
0023: */
0024:
0025: package seda.sandStorm.main;
0026:
0027: import seda.sandStorm.api.*;
0028: import seda.sandStorm.internal.*;
0029: import java.io.*;
0030: import java.util.*;
0031:
0032: /**
0033: * This class is used to pass configuration parameters into Sandstorm
0034: * at startup time. It reads initial configuration parameters from a
0035: * file, using an XML-like format. Various operations can be performed
0036: * upon this class to modify the configuration of the Sandstorm runtime.
0037: *
0038: * @author Matt Welsh
0039: * @see Sandstorm
0040: * @see Main
0041: *
0042: */
0043: public class SandstormConfig implements Cloneable {
0044:
0045: private static final boolean DEBUG = false;
0046: private static final String DELIM_CHAR = ".";
0047: private static final int MAX_DIRECTIVE_LENGTH = 80;
0048: public static final String LIST_ELEMENT_DELIMITER = " ";
0049:
0050: private configSection root;
0051: private Hashtable stages;
0052:
0053: /** Value for defaultThreadMgr to use the thread-per-CPU thread manager. */
0054: public static final String THREADMGR_TPPTM = "TPPTM";
0055: /** Value for defaultThreadMgr to use the thread-per-stage thread manager. */
0056: public static final String THREADMGR_TPSTM = "TPSTM";
0057: /** Value for defaultThreadMgr to use the aggregating TPSTM. */
0058: public static final String THREADMGR_AggTPSTM = "AggTPSTM";
0059:
0060: /** String value for setting boolean configuration entries to true. */
0061: public static final String CONFIG_TRUE = "true";
0062: /** String value for setting boolean configuration entries to false. */
0063: public static final String CONFIG_FALSE = "false";
0064:
0065: /**
0066: * The set of default values for the Sandstorm configuration.
0067: * In order to modify the default configuration used by Sandstorm,
0068: * edit SandstormConfig.java and recompile.
0069: */
0070: public static final String[] defaults = {
0071: "global.defaultThreadManager", THREADMGR_TPSTM,
0072:
0073: "global.threadPool.initialThreads", "1",
0074: "global.threadPool.minThreads", "1",
0075: "global.threadPool.maxThreads", "20",
0076: "global.threadPool.blockTime", "1000",
0077: "global.threadPool.sizeController.enable", CONFIG_FALSE,
0078: "global.threadPool.sizeController.delay", "2000",
0079: "global.threadPool.sizeController.threshold", "1000",
0080: "global.threadPool.sizeController.idleTimeThreshold",
0081: "1000",
0082:
0083: "global.batchController.enable", CONFIG_FALSE,
0084: "global.batchController.minBatch", "1",
0085: "global.batchController.maxBatch", "-1",
0086:
0087: "global.profile.enable", CONFIG_FALSE,
0088: "global.profile.delay", "1000", "global.profile.filename",
0089: "sandstorm-profile.txt", "global.profile.sockets",
0090: CONFIG_FALSE, "global.profile.graph", CONFIG_FALSE,
0091: "global.profile.graphfilename", "sandstorm-graph.txt",
0092:
0093: /* Deprecated */
0094: "global.AggTPSTM.governor.enable", CONFIG_FALSE,
0095: "global.AggTPSTM.governor.delay", "2000",
0096: "global.AggTPSTM.governor.threshold", "1000",
0097:
0098: /* Deprecated */
0099: "global.TPPTM.numCpus", "1", "global.TPPTM.maxThreads",
0100: "1",
0101:
0102: "global.aSocket.enable", CONFIG_TRUE,
0103: "global.aSocket.provider", "NBIO",
0104: "global.aSocket.rateController.enable", CONFIG_FALSE,
0105: "global.aSocket.rateController.rate", "100000.0",
0106:
0107: "global.aDisk.enable", CONFIG_TRUE,
0108: "global.aDisk.threadPool.initialThreads", "1",
0109: "global.aDisk.threadPool.minThreads", "1",
0110: "global.aDisk.threadPool.maxThreads", "20",
0111: "global.aDisk.threadPool.sizeController.enable",
0112: CONFIG_TRUE,
0113: "global.aDisk.threadPool.sizeController.delay", "1000",
0114: "global.aDisk.threadPool.sizeController.threshold", "20", };
0115:
0116: private Hashtable cmdLineArgs;
0117:
0118: /** Default initialization arguments passed to every stage. */
0119: public Hashtable defaultInitArgs;
0120:
0121: /**
0122: * Create a new SandstormConfig with the default settings.
0123: */
0124: public SandstormConfig() {
0125: stages = new Hashtable(1);
0126: root = new configSection("sandstorm");
0127:
0128: // Set default values
0129: for (int i = 0; i < defaults.length; i += 2) {
0130: String key = defaults[i];
0131: String val = defaults[i + 1];
0132: if (getString(key) == null) {
0133: putString(key, val);
0134: }
0135: }
0136: }
0137:
0138: /**
0139: * Create a new SandstormConfig with the default settings, with
0140: * the given default init args, which will be passed to every stage.
0141: * Each element of defaultArgs[] is a String in the format
0142: * "key=value". If "key" contains a dot ("."), then it will be
0143: * treated as a key to be added to the Sandstorm configuration's
0144: * global parameters. Otherwise, the key-value pair will be added to
0145: * the "initargs" section for each stage.
0146: *
0147: */
0148: public SandstormConfig(String defaultArgs[]) throws IOException {
0149: this ();
0150: if (defaultArgs != null)
0151: this .cmdLineArgs = stringArrayToHT(defaultArgs);
0152: }
0153:
0154: /**
0155: * Create a new SandstormConfig, reading the configration from the
0156: * given file. The configuration file uses an XML-like structure;
0157: * see the Sandstorm documentation for more information on its format.
0158: */
0159: public SandstormConfig(String fname) throws IOException {
0160: this ();
0161: readFile(fname);
0162: }
0163:
0164: /**
0165: * Create a new SandstormConfig, reading the configration from the
0166: * given file. The configuration file uses an XML-like structure;
0167: * see the Sandstorm documentation for more information on its format.
0168: *
0169: * @param defaultInitArgs Default initialization arguments passed to
0170: * every stage. These override any arguments found in the config file.
0171: * Each element of this array must be a string with the format
0172: * <tt>"key=value"</tt>.
0173: */
0174: public SandstormConfig(String fname, String defaultArgs[])
0175: throws IOException {
0176: this (defaultArgs);
0177: readFile(fname);
0178: }
0179:
0180: // Return the value associated with the given key in cs; recursive.
0181: private String getVal(configSection cs, String key) {
0182: String car, cdr;
0183: int c = key.indexOf(DELIM_CHAR);
0184: if (c == -1) {
0185: car = key;
0186: cdr = null;
0187: } else {
0188: car = key.substring(0, c);
0189: cdr = key.substring(c + 1, key.length());
0190: }
0191: if (DEBUG)
0192: System.err.println("getVal: cs=" + cs + " key=" + key
0193: + " car=" + car + ", cdr=" + cdr);
0194:
0195: if (cdr == null) {
0196: // OK, we are at a terminal node
0197: return cs.getVal(car);
0198: } else {
0199: // We are at an intermediate node
0200: configSection subsec = cs.getSubsection(car);
0201: if (subsec == null)
0202: return null;
0203: else
0204: return getVal(subsec, cdr);
0205: }
0206: }
0207:
0208: // Set the given value in cs; recursive.
0209: private void putVal(configSection cs, String key, String val) {
0210: String car, cdr;
0211: int c = key.indexOf(DELIM_CHAR);
0212: if (c == -1) {
0213: car = key;
0214: cdr = null;
0215: } else {
0216: car = key.substring(0, c);
0217: cdr = key.substring(c + 1, key.length());
0218: }
0219:
0220: if (cdr == null) {
0221: // OK, we are at a terminal node
0222: cs.putVal(key, val);
0223: return;
0224: } else {
0225: // We are at an intermediate node
0226: configSection subsec = cs.getSubsection(car);
0227: if (subsec == null) {
0228: subsec = new configSection(car);
0229: cs.addSubsection(subsec);
0230: }
0231: putVal(subsec, cdr, val);
0232: return;
0233: }
0234: }
0235:
0236: /**
0237: * Return the configuration option associated with the given key
0238: * as a String. Returns null if not set.
0239: */
0240: public String getString(String key) {
0241: return getString(key, null);
0242: }
0243:
0244: /**
0245: * Return the configuration option associated with the given key
0246: * as a String. Returns default if not set.
0247: */
0248: public String getString(String key, String defaultval) {
0249: String val = getVal(root, key);
0250: if (val == null)
0251: return defaultval;
0252: else
0253: return val;
0254: }
0255:
0256: /**
0257: * Set the given configuration option specified as a String.
0258: */
0259: public void putString(String key, String val) {
0260: putVal(root, key, val);
0261: }
0262:
0263: /**
0264: * Return the configuration option associated with the given key
0265: * as a boolean. Returns false if not set.
0266: */
0267: public boolean getBoolean(String key) {
0268: return getBoolean(key, false);
0269: }
0270:
0271: /**
0272: * Return the configuration option associated with the given key
0273: * as a boolean. Returns default if not set.
0274: */
0275: public boolean getBoolean(String key, boolean defaultval) {
0276: String val = getVal(root, key);
0277: if (val == null)
0278: return defaultval;
0279: if (val.equals("true") || val.equals("TRUE"))
0280: return true;
0281: else
0282: return false;
0283: }
0284:
0285: /**
0286: * Set the given configuration option specified as a boolean.
0287: */
0288: public void putBoolean(String key, boolean val) {
0289: putVal(root, key, (val) ? (CONFIG_TRUE) : (CONFIG_FALSE));
0290: }
0291:
0292: /**
0293: * Return the configuration option associated with the given key
0294: * as an int. Returns -1 if not set or if the value of the key cannot
0295: * be expressed as an int.
0296: */
0297: public int getInt(String key) {
0298: return getInt(key, -1);
0299: }
0300:
0301: /**
0302: * Return the configuration option associated with the given key
0303: * as an int. Returns default if not set or if the value of the
0304: * key cannot be expressed as an int.
0305: */
0306: public int getInt(String key, int defaultval) {
0307: String val = getVal(root, key);
0308: if (val == null)
0309: return defaultval;
0310: try {
0311: return Integer.parseInt(val);
0312: } catch (NumberFormatException nfe) {
0313: return defaultval;
0314: }
0315: }
0316:
0317: /**
0318: * Set the given configuration option specified as an int.
0319: */
0320: public void putInt(String key, int val) {
0321: putVal(root, key, new Integer(val).toString());
0322: }
0323:
0324: /**
0325: * Return the configuration option associated with the given key
0326: * as a double. Returns -1 if not set or if the value of the key cannot
0327: * be expressed as a double.
0328: */
0329: public double getDouble(String key) {
0330: return getDouble(key, -1);
0331: }
0332:
0333: /**
0334: * Return the configuration option associated with the given key
0335: * as a double. Returns default if not set or if the value of the
0336: * key cannot be expressed as a double.
0337: */
0338: public double getDouble(String key, double defaultval) {
0339: String val = getVal(root, key);
0340: if (val == null)
0341: return defaultval;
0342: try {
0343: return new Double(val).doubleValue();
0344: } catch (NumberFormatException nfe) {
0345: return defaultval;
0346: }
0347: }
0348:
0349: /**
0350: * Get the string list value corresponding to the given key.
0351: * Returns null if not set.
0352: */
0353: public String[] getStringList(String key) {
0354: String ret[];
0355: String val = (String) getVal(root, key);
0356: if (val == null)
0357: return null;
0358: StringTokenizer st = new StringTokenizer(val,
0359: SandstormConfig.LIST_ELEMENT_DELIMITER);
0360: Vector v = new Vector(1);
0361: while (st.hasMoreElements()) {
0362: v.addElement(st.nextElement());
0363: }
0364: ret = new String[v.size()];
0365: for (int i = 0; i < ret.length; i++) {
0366: ret[i] = (String) v.elementAt(i);
0367: }
0368: return ret;
0369: }
0370:
0371: /**
0372: * Set the given configuration option specified as an int.
0373: */
0374: public void putDouble(String key, double val) {
0375: putVal(root, key, new Double(val).toString());
0376: }
0377:
0378: /**
0379: * Set the given key to the given string list value.
0380: */
0381: public void puttStringList(String key, String valarr[]) {
0382: String s = "";
0383: for (int i = 0; i < valarr.length; i++) {
0384: s += valarr[i];
0385: if (i != valarr.length - 1)
0386: s += SandstormConfig.LIST_ELEMENT_DELIMITER;
0387: }
0388: putVal(root, key, s);
0389: }
0390:
0391: // Return enumeration of keys matching prefix starting with cs.
0392: // Recursive.
0393: private Enumeration getKeys(configSection cs, String prefix) {
0394: // We are at the end of the prefix
0395: if (prefix == null) {
0396: Vector v = new Vector(1);
0397: Enumeration e = cs.getKeys();
0398: if (e != null) {
0399: while (e.hasMoreElements()) {
0400: v.addElement(e.nextElement());
0401: }
0402: }
0403: e = cs.getSubsections();
0404: if (e != null) {
0405: while (e.hasMoreElements()) {
0406: configSection subsec = (configSection) e
0407: .nextElement();
0408: v.addElement(subsec.getName() + DELIM_CHAR);
0409: }
0410: }
0411: return v.elements();
0412: }
0413:
0414: // First look for single item matching prefix
0415: StringTokenizer st = new StringTokenizer(prefix, DELIM_CHAR);
0416: String tok = st.nextToken();
0417: if (tok == null)
0418: return null;
0419: String val = cs.getVal(tok);
0420: if (val != null) {
0421: Vector v = new Vector(1);
0422: v.addElement(tok);
0423: return v.elements();
0424: }
0425:
0426: // Look for subsection matching prefix
0427: configSection subsec = cs.getSubsection(tok);
0428: if (subsec == null)
0429: return null;
0430: String tok2 = st.nextToken();
0431: return getKeys(subsec, tok2);
0432: }
0433:
0434: /**
0435: * Return an enumeration of the keys matching the given prefix.
0436: * A given key maps onto a set of child keys if it ends in a
0437: * "." character (that is, it is an internal node within the tree).
0438: * A key not ending in "." is a terminal node and maps onto a
0439: * value that may be obtained using getString, getInt, or getDouble.
0440: */
0441: public Enumeration getKeys(String prefix) {
0442: return getKeys(root, prefix);
0443: }
0444:
0445: /**
0446: * Return an enumeration of the top-level keys in this configuration.
0447: */
0448: public Enumeration getKeys() {
0449: return getKeys(root, null);
0450: }
0451:
0452: /**
0453: * Return a copy of this object.
0454: */
0455: public SandstormConfig getCopy() {
0456: try {
0457: return (SandstormConfig) (this .clone());
0458: } catch (CloneNotSupportedException e) {
0459: throw new Error(
0460: "Internal error: SandstormConfig must support clone!");
0461: }
0462: }
0463:
0464: /**
0465: * Add a stage to this SandstormConfig.
0466: *
0467: * @param stageName The name of the stage as it should be registered.
0468: * @param className The fully-qualified class name of the stage event
0469: * handler.
0470: * @param initargs The initial arguments to pass into the stage.
0471: */
0472: public void addStage(String stageName, String className,
0473: String initargs[]) throws StageNameAlreadyBoundException,
0474: IOException {
0475: if (stages.get(stageName) != null) {
0476: throw new StageNameAlreadyBoundException("Stage "
0477: + stageName
0478: + " already registered in SandstormConfig");
0479: }
0480:
0481: stageDescr descr = new stageDescr();
0482: descr.stageName = stageName;
0483: descr.className = className;
0484: descr.initargs = stringArrayToHT(initargs);
0485: stages.put(stageName, descr);
0486: }
0487:
0488: /**
0489: * Return an Enumeration of the stages specified by this SandstormConfig.
0490: */
0491: public Enumeration getStages() {
0492: return stages.elements();
0493: }
0494:
0495: /**
0496: * Read the configuration from the given file.
0497: */
0498: public void readFile(String fname) throws IOException {
0499:
0500: Reader in = new directiveReader(new BufferedReader(
0501: new FileReader(fname)));
0502: root = new configSection(in);
0503:
0504: configSection global_initargs = null;
0505:
0506: if (!root.getName().equals("sandstorm"))
0507: throw new IOException(
0508: "Outermost section config file named "
0509: + root.getName() + ", expecting sandstorm");
0510:
0511: // Set default values
0512: for (int i = 0; i < defaults.length; i += 2) {
0513: String key = defaults[i];
0514: String val = defaults[i + 1];
0515: if (getString(key) == null) {
0516: putString(key, val);
0517: }
0518: }
0519:
0520: // Set command line values
0521: this .defaultInitArgs = new Hashtable();
0522: if (cmdLineArgs != null) {
0523: Enumeration e = cmdLineArgs.keys();
0524: while (e.hasMoreElements()) {
0525: String key = (String) e.nextElement();
0526: if (key.indexOf('.') != -1) {
0527: putString(key, (String) cmdLineArgs.get(key));
0528: } else {
0529: this .defaultInitArgs.put(key, (String) cmdLineArgs
0530: .get(key));
0531: }
0532: }
0533: }
0534:
0535: if (DEBUG) {
0536: System.err.println("DOING DUMP: -----------------------");
0537: root.dump();
0538: System.err.println("DONE WITH DUMP ---------------------");
0539: }
0540:
0541: // Get global init args
0542: configSection global = root.getSubsection("global");
0543: if (global != null) {
0544: global_initargs = global.getSubsection("initargs");
0545: }
0546:
0547: // Get stages
0548: if (DEBUG)
0549: System.err.println("Parsing stages");
0550: configSection stagesec = root.getSubsection("stages");
0551: if (stagesec != null) {
0552: for (int i = 0; i < stagesec.subsections.size(); i++) {
0553: configSection sec = (configSection) stagesec.subsections
0554: .elementAt(i);
0555: stageDescr descr = new stageDescr();
0556: descr.stageName = sec.getName();
0557: descr.className = sec.getVal("class");
0558: if (descr.className == null)
0559: throw new IOException(
0560: "Missing class name in <stage> section of config file");
0561: if (DEBUG)
0562: System.err.println("Parsing stage "
0563: + descr.stageName);
0564:
0565: descr.initargs = new Hashtable(1);
0566:
0567: // Add global args
0568: if (global_initargs != null) {
0569: Enumeration e2 = global_initargs.getKeys();
0570: while (e2.hasMoreElements()) {
0571: String key = (String) e2.nextElement();
0572: String val = global_initargs.getVal(key);
0573: descr.initargs.put(key, val);
0574: }
0575: }
0576:
0577: // Add stage-specific args
0578: configSection args = sec.getSubsection("initargs");
0579: if (args != null) {
0580: Enumeration e2 = args.getKeys();
0581: while (e2.hasMoreElements()) {
0582: String key = (String) e2.nextElement();
0583: String val = args.getVal(key);
0584: descr.initargs.put(key, val);
0585: }
0586: }
0587:
0588: // Add defaultInitArgs
0589: if (defaultInitArgs != null) {
0590: Enumeration e2 = defaultInitArgs.keys();
0591: while (e2.hasMoreElements()) {
0592: String key = (String) e2.nextElement();
0593: String val = (String) defaultInitArgs.get(key);
0594: descr.initargs.put(key, val);
0595: }
0596: }
0597:
0598: try {
0599: String val = sec.getVal("queueThreshold");
0600: if (val == null)
0601: descr.queueThreshold = -1;
0602: else
0603: descr.queueThreshold = Integer.parseInt(val);
0604: } catch (NumberFormatException ne) {
0605: descr.queueThreshold = -1;
0606: }
0607: if (DEBUG)
0608: System.err.println("Adding stage "
0609: + descr.stageName);
0610: stages.put(descr.stageName, descr);
0611: }
0612: }
0613: }
0614:
0615: // ----------------------------------------------------------------------
0616:
0617: // Convert an array of "key=value" strings to a Hashtable
0618: private Hashtable stringArrayToHT(String arr[]) throws IOException {
0619: if (arr == null)
0620: return null;
0621: Hashtable ht = new Hashtable(1);
0622: for (int i = 0; i < arr.length; i++) {
0623: StringTokenizer st = new StringTokenizer(arr[i], "=");
0624: String key;
0625: String val;
0626: try {
0627: key = st.nextToken();
0628: val = st.nextToken();
0629: while (st.hasMoreTokens())
0630: val += "=" + st.nextToken();
0631: } catch (NoSuchElementException e) {
0632: throw new IOException("Could not convert string '"
0633: + arr[i] + "' to key=value pair");
0634: }
0635: ht.put(key, val);
0636: }
0637: return ht;
0638: }
0639:
0640: // Internal class to represent configuration file format
0641: class configSection {
0642: private String secname;
0643: private StreamTokenizer tok;
0644: private Vector subsections;
0645: private Hashtable vals;
0646:
0647: private configSection() {
0648: subsections = new Vector(1);
0649: vals = new Hashtable(1);
0650: }
0651:
0652: configSection(Reader in) throws IOException {
0653: this ();
0654: tok = new StreamTokenizer(in);
0655: tok.resetSyntax();
0656: tok.wordChars((char) 0, (char) 255);
0657: tok.whitespaceChars('\u0000', '\u0020');
0658: tok.commentChar('#');
0659: tok.eolIsSignificant(true);
0660: doRead();
0661: }
0662:
0663: private configSection(StreamTokenizer tok) throws IOException {
0664: this ();
0665: this .tok = tok;
0666: tok.pushBack();
0667: tok.wordChars('0', '9');
0668: doRead();
0669: }
0670:
0671: configSection(String name) {
0672: this ();
0673: this .secname = name;
0674: }
0675:
0676: String getName() {
0677: return secname;
0678: }
0679:
0680: configSection getSubsection(String name) {
0681: for (int i = 0; i < subsections.size(); i++) {
0682: configSection sec = (configSection) subsections
0683: .elementAt(i);
0684: if (sec.getName().equals(name))
0685: return sec;
0686: }
0687: return null;
0688: }
0689:
0690: void addSubsection(configSection subsec) {
0691: subsections.addElement(subsec);
0692: }
0693:
0694: // Return the string associated with key in this section
0695: // If not specified, null is returned
0696: String getVal(String key) {
0697: return (String) vals.get(key);
0698: }
0699:
0700: Enumeration getSubsections() {
0701: if (subsections == null)
0702: return null;
0703: return subsections.elements();
0704: }
0705:
0706: Enumeration getKeys() {
0707: return vals.keys();
0708: }
0709:
0710: int numKeys() {
0711: return vals.size();
0712: }
0713:
0714: Hashtable getVals() {
0715: return vals;
0716: }
0717:
0718: void putVal(String key, String val) {
0719: vals.put(key, val);
0720: }
0721:
0722: // Read next section name, parse recursively until we see the
0723: // end of that section
0724: private void doRead() throws IOException {
0725: String word, key, value;
0726: boolean read_secname = false;
0727:
0728: // Get initial section name
0729: word = nextWord();
0730: if (word.startsWith("<") && word.endsWith(">")) {
0731: secname = word.substring(1, word.length() - 1);
0732: } else {
0733: throw new IOException("No section name found at line "
0734: + tok.lineno() + " of config file, read "
0735: + word);
0736: }
0737:
0738: boolean done = false;
0739: while (!done) {
0740:
0741: key = null;
0742: while (true) {
0743: // Read key
0744: word = nextWord();
0745: if (word.startsWith("<") && word.endsWith(">")) {
0746: String val = word.substring(1,
0747: word.length() - 1);
0748: if (val.equals("/" + secname)) {
0749: // Done reading this section
0750: done = true;
0751: break;
0752: } else {
0753: // Found a new section; recurse
0754: configSection subsec = new configSection(
0755: tok);
0756: if (getSubsection(subsec.getName()) != null) {
0757: throw new IOException("subsection "
0758: + subsec.getName()
0759: + " redefined at line "
0760: + tok.lineno()
0761: + " of config file");
0762: }
0763: if (vals.get(subsec.getName()) != null) {
0764: throw new IOException("subsection "
0765: + subsec.getName()
0766: + " conflicts with key "
0767: + subsec.getName()
0768: + " at line " + tok.lineno()
0769: + " of config file");
0770: }
0771:
0772: subsections.addElement(subsec);
0773: }
0774: } else {
0775: key = word;
0776: break;
0777: }
0778: }
0779:
0780: if (done)
0781: break;
0782:
0783: // Read value
0784: word = nextLine();
0785: if (word.startsWith("<") && word.endsWith(">")) {
0786: // Bad format: Should not have section tag here
0787: throw new IOException("Unexpected section tag "
0788: + word + " on line " + tok.lineno()
0789: + " of config file");
0790: } else {
0791: value = word;
0792: }
0793:
0794: if (key == null)
0795: throw new IOException("key is null at line "
0796: + tok.lineno() + " of config file");
0797: if (vals.get(key) != null) {
0798: throw new IOException("key " + key
0799: + " redefined at line " + tok.lineno()
0800: + " of config file");
0801: }
0802: if (getSubsection(key) != null) {
0803: throw new IOException("key " + key
0804: + " conflicts with subsection " + key
0805: + " at line " + tok.lineno()
0806: + " of config file");
0807: }
0808: if (key.indexOf(DELIM_CHAR) != -1) {
0809: throw new IOException("key " + key
0810: + " may not contain character '"
0811: + DELIM_CHAR + "' at line " + tok.lineno()
0812: + " of config file");
0813: }
0814: vals.put(key, value);
0815: }
0816:
0817: }
0818:
0819: // Read next whitespace-delimited word from tok
0820: private String nextWord() throws IOException {
0821: while (true) {
0822: int type = tok.nextToken();
0823: switch (type) {
0824:
0825: case StreamTokenizer.TT_EOL:
0826: continue;
0827:
0828: case StreamTokenizer.TT_EOF:
0829: throw new EOFException("EOF in config file");
0830:
0831: case StreamTokenizer.TT_WORD:
0832: if (DEBUG)
0833: System.err.println("nextWord returning "
0834: + tok.sval);
0835: return tok.sval;
0836:
0837: case StreamTokenizer.TT_NUMBER:
0838: if (DEBUG)
0839: System.err.println("nextWord returning number");
0840: return Double.toString(tok.nval);
0841:
0842: default:
0843: continue;
0844: }
0845: }
0846: }
0847:
0848: // Read rest of line from tok
0849: private String nextLine() throws IOException {
0850: String line = new String("");
0851: boolean first = true;
0852:
0853: while (true) {
0854: switch (tok.nextToken()) {
0855:
0856: case StreamTokenizer.TT_EOL:
0857: if (DEBUG)
0858: System.err
0859: .println("nextLine returning " + line);
0860: return line;
0861:
0862: case StreamTokenizer.TT_EOF:
0863: throw new EOFException("EOF in config file");
0864:
0865: case StreamTokenizer.TT_WORD:
0866: if (first) {
0867: line = tok.sval;
0868: first = false;
0869: } else {
0870: line += " " + tok.sval;
0871: }
0872: break;
0873:
0874: case StreamTokenizer.TT_NUMBER:
0875: if (first) {
0876: line = Double.toString(tok.nval);
0877: first = false;
0878: } else {
0879: line += " " + Double.toString(tok.nval);
0880: }
0881: break;
0882:
0883: default:
0884: continue;
0885: }
0886: }
0887: }
0888:
0889: // Debugging only
0890: void dump() {
0891: System.err.println("<" + secname + ">");
0892: Enumeration e = vals.keys();
0893: while (e.hasMoreElements()) {
0894: String key = (String) e.nextElement();
0895: String val = (String) vals.get(key);
0896: System.err.println(" " + key + " " + val);
0897: }
0898:
0899: for (int i = 0; i < subsections.size(); i++) {
0900: configSection sec = (configSection) subsections
0901: .elementAt(i);
0902: sec.dump();
0903: }
0904: System.err.println("</" + secname + ">");
0905: }
0906:
0907: public String toString() {
0908: return "configSection <" + secname + ">";
0909: }
0910: }
0911:
0912: /**
0913: * Internal class to preprocess special directives in the
0914: * config file.
0915: */
0916: class directiveReader extends Reader {
0917: private Reader under, includedFile, markStream;
0918: private boolean markIsIncluded = false, closed = false;
0919: private boolean inComment = false;
0920:
0921: directiveReader(Reader under) throws IOException {
0922: this .under = under;
0923: if (!under.markSupported()) {
0924: throw new IOException(
0925: "SandstormConfig: Internal error: directiveReader.under must support mark() -- contact mdw@cs.berkeley.edu");
0926: }
0927: }
0928:
0929: public int read() throws IOException {
0930: if (closed)
0931: throw new IOException("directiveReader is closed");
0932: if (includedFile != null) {
0933: int ret = includedFile.read();
0934: if (ret == -1)
0935: includedFile = null;
0936: else
0937: return ret;
0938: }
0939:
0940: boolean done = false;
0941:
0942: while (!done) {
0943:
0944: int c = under.read();
0945:
0946: // Ignore special directives inside of comments
0947: if (c == '#') {
0948: inComment = true;
0949: }
0950: if (c == '\n') {
0951: inComment = false;
0952: }
0953:
0954: if (!inComment && (c == '<')) {
0955: under.mark(100);
0956: if (under.read() == '!') {
0957: // Process special directive; read until '>'
0958: String directive = "<!";
0959: char c1 = ' ';
0960: while (c1 != '>') {
0961: try {
0962: c1 = (char) under.read();
0963: if (c1 == -1)
0964: throw new IOException("End of file");
0965: } catch (IOException ioe) {
0966: throw new IOException(
0967: "SandstormConfig: Unterminated directive "
0968: + directive
0969: .substring(
0970: 0,
0971: Math
0972: .min(
0973: directive
0974: .length(),
0975: 10))
0976: + " in configuration file");
0977: }
0978: directive += c1;
0979: }
0980: if (DEBUG)
0981: System.err
0982: .println("Got special directive: "
0983: + directive);
0984:
0985: if (directive.startsWith("<!include")) {
0986: StringTokenizer st = new StringTokenizer(
0987: directive);
0988: String dir = st.nextToken();
0989: String fname = st.nextToken();
0990: fname = fname.substring(0,
0991: fname.length() - 1).trim();
0992: if (DEBUG)
0993: System.err.println("Including file: "
0994: + fname);
0995: includedFile = new directiveReader(
0996: new BufferedReader(new FileReader(
0997: fname)));
0998: int ret = includedFile.read();
0999: if (ret == -1) {
1000: includedFile = null;
1001: continue;
1002: } else {
1003: return ret;
1004: }
1005: } else {
1006: throw new IOException(
1007: "SandstormConfig: Unrecognized directive "
1008: + directive
1009: + " in config file");
1010: }
1011:
1012: } else {
1013: // Got a '<' with no following '!'
1014: under.reset();
1015: return c;
1016: }
1017: } else {
1018: // Got something other than '<'
1019: return c;
1020: }
1021: }
1022: // Should never get here
1023: return -1;
1024: }
1025:
1026: public int read(char cbuf[]) throws IOException {
1027: return read(cbuf, 0, cbuf.length);
1028: }
1029:
1030: public int read(char cbuf[], int off, int len)
1031: throws IOException {
1032: int n = 0;
1033: for (int i = off; i < len; i++) {
1034: int c = read();
1035: if (cbuf[i] == -1)
1036: return n;
1037: cbuf[i] = (char) c;
1038: n++;
1039: }
1040: return n;
1041: }
1042:
1043: public long skip(long n) throws IOException {
1044: if (n < 0)
1045: throw new IllegalArgumentException(
1046: "directiveReader.skip: n must be nonzero");
1047: long skipped = 0;
1048: for (long l = n; l >= 0; l--) {
1049: int c = read();
1050: if (c == -1)
1051: return skipped;
1052: skipped++;
1053: }
1054: return skipped;
1055: }
1056:
1057: public boolean ready() throws IOException {
1058: if (includedFile != null)
1059: return includedFile.ready();
1060: return under.ready();
1061: }
1062:
1063: public boolean markSupported() {
1064: return true;
1065: }
1066:
1067: public void mark(int readAheadLimit) throws IOException {
1068: if (includedFile != null) {
1069: markStream = includedFile;
1070: markIsIncluded = true;
1071: } else {
1072: markStream = under;
1073: }
1074: markStream.mark(readAheadLimit);
1075: }
1076:
1077: public void reset() throws IOException {
1078: markStream.reset();
1079: if (markIsIncluded)
1080: includedFile = markStream;
1081: }
1082:
1083: public void close() throws IOException {
1084: if (includedFile != null)
1085: includedFile.close();
1086: under.close();
1087: closed = true;
1088: }
1089: }
1090:
1091: }
|