001: /*
002: * <copyright>
003: *
004: * Copyright 2002-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.core.adaptivity;
028:
029: import java.io.IOException;
030: import java.io.Reader;
031: import java.io.StreamTokenizer;
032: import java.util.ArrayList;
033: import java.util.List;
034:
035: import org.cougaar.core.service.LoggingService;
036:
037: /**
038: * Parser for plays to be stored in a Playbook. The grammar is
039: * straightforward with the exception of ranges and range lists.
040: * <h3>Basic Syntax</h3>
041: * <p>Each play consists of an if clause (predicate) and one or more
042: * constraint phrases. These are all separated by colons and
043: * terminated with a semi-colon. For example:</p>
044: * <pre><if clause>:<constraint>:...<constraint>;</pre><p>
045: * The <if clause> is an expression involving {@link Condition}s and
046: * constants that must evaluate to true or false. A constraint
047: * consists of an {@link OperatingMode} following by a
048: * ConstraintOperator and a range list and signifies that the
049: * OperatingMode will be constrained so as to satisfy the relation
050: * specified by the operator to the range list. For example:</p>
051: * <pre>
052: * FooPlugin.MODE < 5;
053: * FooPlugin.MODE in {1 to 7};
054: * FooPlugin.SPEED = "FAST";</pre><p>
055: * Note that both relational and range operators (==, not it, etc.) as
056: * well as assignment may be used. Assignment explicitly specifies the
057: * allowed values. Relational and range operators implicitly specify
058: * the allowed values by specifying the test that any such value must
059: * satisfy. The operators =, ==, and in are all equivalent as are !=
060: * and not in. The other relational operators are usually used with
061: * single valued range lists (constants).
062: * <h3>Comments</h3><p>
063: * Comments are ignored by the parser. Both slash-slash and slash-star
064: * comments are recognized. The former beging with two adjacent
065: * slashes and terminate at the end of line. The latter terminate with
066: * a star-slash sequence.</p>
067: * <h3>Numeric Constants</h3><p>
068: * Numeric constants are interpreted as floating point numbers
069: * (doubles). When necessary, numbers will be automatically cast to integers or
070: * longs. A consequence of this is that plays may not use the full
071: * precision offered by longs (64 bits) and are restricted to the 56
072: * bits of precision in a double.
073: * <h3>Ranges</h3><p>
074: * A range is written as two numbers separated by the keyword "to" or
075: * "thru". The former excludes the end point and the latter includes
076: * the end point. A point range (a range having exactly one value) may
077: * be written as a single number; 7 is equivalent to 7 thru 7. 7 to 7,
078: * on the other hand, is an empty range; it allows no values.
079: * <h3>Range Lists</h3><p>
080: * A range list is a sequence of ranges enclosed in braces. The
081: * elements of a range list may be separated by commas, but they are
082: * not required; white space is sufficient. A range
083: * list consisting of exactly one range my omit the braces.
084: * Consequently, a numeric constant is also a range list. Range lists
085: * frequently have elements that are point ranges and express list of
086: * descrete values rather than a continuum. Range lists
087: * may appear only as the right-hand operand of relational operators
088: * (see below). Numeric constants may appear in arithmetic expressions.
089: * to the written value. In general the context makes it clear which
090: * should be used, but for example:</p><pre>
091: * Foo < {11 to 20, 25 thru 30, 1 to 3};</pre><p>
092: * is true if Foo is less than 1 (the minimum of the ranges). This
093: * characteristic is an artifact of the parser and probably
094: * insignificant to the playbook writer.</p><p>
095: * As implied above, a range list consists of one or more ranges
096: * enclosed in braces. The comma between the ranges is optional (white
097: * space is sufficient). The braces are also optional if the list has
098: * a single range. Each range consists of either one number or string
099: * or two numbers or strings separated by either "to" or "thru". "To"
100: * signifies a range that does not include the end point whereas
101: * "thru" signifies a range that does include the end point. If the
102: * number is floating point, only the exact value given by the end
103: * point is excluded. The next smaller value that can be represented
104: * is always included. This characteristic can be used to insure there
105: * are no gaps in the coverage of a series of predicates. For
106: * example:</p><pre>
107: * x in {1 to 3, 5 to 10}:...;
108: * x in {3 to 5}:...;
109: * x >= 10:...;</pre><p>insures that exactly one of the predicates
110: * is true for any
111: * value of x from 1 to infinity. There is no value of x that can fall
112: * into a crack in the vicinity of 3 or 5 or 10 nor is there any value of x
113: * in those same regions that can cause two predicates to
114: * fire.</p><p>Numbers are parsed as doubles and coerced to other
115: * numeric typs as needed. This means that long values cannot be
116: * written with their entire range. (Doubles can exactly represent
117: * only 56 bits of precision.)</p>
118: * <h3>Strings</h3><p>Strings are used as constants (range limits),
119: * Condition names and OperatingMode names. The interpretation depends
120: * on context (see below). Strings do not need to be quoted unless
121: * they contain characters (such as spaces) that have syntactic
122: * meaning to the parser. In particular, strings need not be quoted
123: * when they contain . (period) and [] (brackets). All other
124: * punctuation and special characters should be quoted.</p><p>
125: * String constants can be used in range lists and arithmetic
126: * expressions using the + operator. Strings cannot be used in other
127: * arithmetic expressions. String comparisons are usually
128: * confined to equality and inequality tests, but the other operators
129: * have a defined meaning (alphabetic comparison using the default
130: * collation sequence). String ranges are rare, but if used, have a
131: * slightly different meaning when "to" ranges are specified because
132: * the highest value included in a "to" range would be infinitely
133: * long. For example, the last string in the range "bar" to "foo"
134: * would be "fon\uffff\uffff\uffff\uffff...". It is hard to conceive of
135: * a use for a "to" range involving strings, so the infinite string is
136: * truncated after the first "\uffff".</p>
137: * Strings that name Conditions <em>can</em> be used in arithmetic
138: * expressions if the named Condition has a numeric value. If the
139: * named Condition has a String value, then only the + operator is
140: * allowed.
141: * <h3>If Clause Operators</h3>
142: * <h4>Arithmetic Operators (+, -, *, /)</h4><p>
143: * These have their standard meanings. The parser treats these with
144: * standard precedence so parentheses are needed in the usual places.
145: * When in doubt, parenthesize. There is no modulus operator (%).</p>
146: * <h4>Relational Operators (< <= == != >= >)</h4><p>
147: * Relational operators compare two quantities and yield a boolean
148: * (true or false) result. Relational operators are <em>not</em>
149: * commutative; the interpretation of the left hand operand is
150: * different from the right hand operand. Strings in the left hand
151: * operand always name Conditions in if clauses or OperatingModes in
152: * constraints. Strings in the right hand operand are always values
153: * (or range limits). So, for example, in the play:</p><pre>
154: * FOO == HIGH: HIGH = FOO;</pre><p>The first FOO is the name of a
155: * Condition, the second FOO is a value to be stored in the
156: * OperatingMode named HIGH when the Condition named FOO has a value
157: * equal to HIGH. For clarity, it is a good practice to quote strings
158: * used as values and not quote strings that denote Conditions and
159: * Operating Modes.
160: </p><p>Comparison of strings uses the default
161: * collation sequence for characters.</p><h4>
162: * Range Operators (in and not in)</h4><p>
163: * Range operators test for inclusion in (or exclusion from) a range
164: * list. The meaning is straightforward:</p><pre>
165: * x in {1 thru 7, 10}</pre><p>is true iff
166: * x has a value between 1 and 7 inclusive or has the value 10. A
167: * range list is just a shorthand for a combination of less than and
168: * greater than terms, but is easier to write and process.</p>
169: * <h4>Boolean Constants</h4><p>The boolean constants true and false
170: * may be used in an if clause. This is useful when developing a
171: * playbook before the details are worked out or to deactivate certain
172: * plays.</p>
173: * <h3>Constraints</h3><p>
174: * Constraints specify an allowed list of ranges to which an
175: * <code>OperatingMode</code> can be set. The constraints from all the
176: * plays (including those manufactured from policies) are combined
177: * (intersected) to form the final constraint. Often this final
178: * constraint is a single value, but when it has multiple values, the
179: * minimum of the first range in the list is used. The constraint can
180: * be viewed either as an expression that must be true or as an
181: * assignment of a range list to the <code>OperatingMode</code>. For
182: * example:</p><pre>
183: * Opmode1 = 3
184: * Opmode1 in {3 thru 3}
185: * Opmode1 == 3</pre><p>
186: * are all equivalent. The first assigns the single valued range {3
187: * thru 3} to Opmode1. The second requires that Opmode1 have the value
188: * 3 so that is in the range 3 thru 3. The third requires that Opmode1
189: * be exactly equal to 3.</p><p>All forms of constraints are rewritten
190: * as an assignment of a range list to the <code>OperatingMode</code>.
191: * For example:</p><pre>
192: * Opmode < 3</pre><p>
193: * is rewritten as:</p><pre>
194: * Opmode = {<negative infinity> to 3}</pre><p>
195: * The process of intersecting the constraints from several plays may
196: * not be clear. For example, if the if clauses of two places yielded
197: * these two constraints:</p><pre>
198: * opmode in {56, 128, 256, 1024}
199: * opmode >= 128</pre><p>The combined constraint would be:</p><pre>
200: * opmode in {128, 256, 1024}</pre><p>Another example:</p><pre>
201: * opmode in 1 thru 10
202: * opmode in 5 to 20</pre><p>The combined result would be:
203: * opmode in 5 thru 10</pre><p>For a value to be included in the
204: * combined range list, it must be included by the range lists of all
205: * the selected plays. It is a playbook-writing error to allow plays
206: * to be simultaneously active for which the combined range list is
207: * empty. For example:</p><pre>
208: * opmode in {56, 128, 256}
209: * opmode < 56</pre><p>would be an error. Such errors are not detected
210: * until they occur. When they do occur, they are logged and the later
211: * constraint is ignored.</p><h3>OperatingModeConditions</h3><p>
212: * OperatingModeConditions are intermediate variables the act as
213: * OperatingModes in some plays and Conditions in later plays. This
214: * allows certain plays such as those that result from policies
215: * received from other agents to set the value of an agent-wide
216: * OperatingMode that is then translated into component-specific
217: * OperatingModes by using the agent-wide mode as a Condition in other
218: * plays. For example, assume the following plays resulting from
219: * inter-agent operating mode policies:</p><pre>
220: * Threatcon > 3: DefensePosture = 2;
221: * Threatcon <= 3: DefensePosture = 1;</pre><p>
222: * and the following plays from the local playbook:</p><pre>
223: * DefensePosture < 2: FooPlugin.keyLength = 56: AdaptivePlugin.fidelity = high;
224: * DefensePosture >= 2: FooPlugin.keylength = 128: AdaptivePlugin.fidelity = medium;</pre><p>
225: * The allows the outside agent to be unaware of the details of the
226: * makeup of the agent by expressing its policy in terms of a
227: * "DefensePosture" concept. However, the local playbook is developed with an
228: * awareness of the agent makeup so it can contain plays to set the
229: * operating modes of its plugins based on this
230: * DefensePosture value.</p><h3>OperatingModePolicy</h3><p>
231: * An OperatingModePolicy has the same syntax as a Play except that it
232: * is prefixed with a name.</p>
233: **/
234: public class Parser {
235: StreamTokenizer st;
236: boolean pushedBack = false;
237: ConstrainingClause cc;
238: private LoggingService logger;
239:
240: /**
241: * Construct from a Reader.
242: * @param s the Reader to read from
243: * @param logger a LoggingService onto which error and debug
244: * information will be written.
245: **/
246: public Parser(Reader s, LoggingService logger) {
247: this .logger = logger;
248: st = new StreamTokenizer(s);
249: st.wordChars('_', '_');
250: st.wordChars('[', '[');
251: st.wordChars(']', ']');
252: st.ordinaryChars('/', '/');
253: st.slashStarComments(true);
254: st.slashSlashComments(true);
255: st.quoteChar('"');
256: st.quoteChar('\'');
257: }
258:
259: /**
260: * Parses and returns the next ConstrainingClause. The terminator
261: * (colon) is not consumed.
262: * @return the parsed constraining clause.
263: * @throws IOException if an error occurs reading from the input.
264: **/
265: public ConstrainingClause parseConstrainingClause()
266: throws IOException {
267: cc = new ConstrainingClause();
268: parse(Operator.MAXP);
269: ConstrainingClause result = cc;
270: cc = null;
271: return result;
272: }
273:
274: /**
275: * Parse a series of constraints from the Reader up through a
276: * semicolon. The terminating semicolon is consumed.
277: * @return an array of ConstraintPhrases parsed from the input.
278: * @throws IOException if an error occurs reading from the input.
279: **/
280: public ConstraintPhrase[] parseConstraints() throws IOException {
281: List constraintPhrases = new ArrayList();
282: while (true) {
283: int token = nextToken();
284: if (token == ';') {
285: break;
286: }
287: if (token != ':')
288: throw unexpectedTokenException("Missing semicolon");
289: constraintPhrases.add(parseConstraintPhrase());
290: }
291:
292: return (ConstraintPhrase[]) constraintPhrases
293: .toArray(new ConstraintPhrase[constraintPhrases.size()]);
294: }
295:
296: /**
297: * Parses a single, complete Play from the input. The terminating
298: * semicolon is consumed.
299: * @return the parsed Play.
300: * @throws IOException if an error occurs reading from the input.
301: **/
302: public Play parsePlay() throws IOException {
303: ConstrainingClause cc = parseConstrainingClause(); // Parse the ifClause
304: ConstraintPhrase[] cp = parseConstraints();
305: return new Play(cc, cp);
306: }
307:
308: /**
309: * Parses an entire file of Plays and returns them as an array.
310: * @return an array of plays from the file.
311: * @throws IOException if an error occurs reading from the input.
312: **/
313: public Play[] parsePlays() throws IOException {
314: List plays = new ArrayList();
315: readPlays: while (true) {
316: try {
317: plays.add(parsePlay());
318: } catch (IllegalArgumentException e) {
319: if (logger.isDebugEnabled()) {
320: logger.debug("Parse exception", e);
321: } else {
322: logger.error(e.getMessage());
323: }
324: while (st.ttype != ';') {
325: if (st.ttype == StreamTokenizer.TT_EOF)
326: break readPlays;
327: }
328: }
329: if (nextToken() == StreamTokenizer.TT_EOF)
330: break;
331: pushBack();
332: }
333: return (Play[]) plays.toArray(new Play[plays.size()]);
334: }
335:
336: /**
337: * Parses an OperatingModePolicy. Syntactically, an
338: * OperatingModePolicy is identical with a play except it is
339: * prefixed with a name token. The name token must be a string
340: * (quoted as necessary).
341: * @return an OperatingModePolicy
342: * @throws IOException if an error occurs reading from the input.
343: **/
344: public OperatingModePolicy parseOperatingModePolicy()
345: throws IOException {
346: // pull the first token, assume it is the policy name
347: String policyName = null;
348: if (isWord(nextToken())) {
349: policyName = st.sval;
350: } else {
351: pushBack();
352: }
353: ConstrainingClause cc = parseConstrainingClause(); // Parse the ifClause
354: ConstraintPhrase[] cp = parseConstraints();
355: return new OperatingModePolicy(policyName, cc, cp);
356: }
357:
358: /**
359: * Parses an entire file of OperatingModePolicies and returns them
360: * as an array.
361: * @return an array of OperatingModePolicies from the file.
362: * @throws IOException if an error occurs reading from the input.
363: **/
364: public OperatingModePolicy[] parseOperatingModePolicies()
365: throws IOException {
366: List policies = new ArrayList();
367: while (true) {
368: if (nextToken() == StreamTokenizer.TT_EOF)
369: break;
370: pushBack();
371: policies.add(parseOperatingModePolicy());
372: }
373: return (OperatingModePolicy[]) policies
374: .toArray(new OperatingModePolicy[policies.size()]);
375: }
376:
377: /**
378: * Parse an if clause and push it onto the ConstrainingClause.
379: * @param lp left left precedence of operator calling this method.
380: */
381: private void parse(int lp) throws IOException {
382: if (logger.isDebugEnabled()) {
383: String caller = new Throwable().getStackTrace()[1]
384: .toString();
385: logger.debug("Parse from " + caller + " " + lp);
386: }
387: try {
388: int token = nextToken();
389: switch (token) {
390: default:
391: throw unexpectedTokenException("Unexpected token");
392: case '!':
393: parse(BooleanOperator.NOT.getLP());
394: cc.push(BooleanOperator.NOT);
395: break;
396: case '-':
397: parse(ArithmeticOperator.NEGATE.getLP());
398: cc.push(ArithmeticOperator.NEGATE);
399: break;
400: case '(':
401: parse(Operator.MAXP);
402: token = nextToken();
403: if (token != ')')
404: throw unexpectedTokenException("Missing close paren");
405: break;
406: case '"':
407: case '\'':
408: case StreamTokenizer.TT_WORD:
409: if (st.sval.equalsIgnoreCase(BooleanOperator.TRUE
410: .toString())) {
411: cc.push(BooleanOperator.TRUE);
412: break;
413: }
414: if (st.sval.equalsIgnoreCase(BooleanOperator.FALSE
415: .toString())) {
416: cc.push(BooleanOperator.FALSE);
417: break;
418: }
419: cc.push(st.sval);
420: break;
421: case StreamTokenizer.TT_NUMBER:
422: cc.push(new Double(st.nval));
423: break;
424: }
425: while (true) {
426: token = nextToken(); // Operator
427: if (token == ':') {
428: pushBack();
429: return;
430: }
431: Operator op;
432: switch (token) {
433: default:
434: pushBack();
435: return;
436: case '"':
437: case '\'':
438: case StreamTokenizer.TT_WORD:
439: if (!st.sval.equalsIgnoreCase("in")
440: && !st.sval.equalsIgnoreCase("not")) {
441: pushBack();
442: return;
443: }
444: // Fall thru into parseConstraintOpValue
445: case '<':
446: case '>':
447: case '=':
448: case '!':
449: if (ConstraintOperator.IN.getRP() >= lp) {
450: // Not yet
451: pushBack();
452: return;
453: }
454: pushBack();
455: cc.push(parseConstraintOpValue(null));
456: return;
457: case '&':
458: op = BooleanOperator.AND;
459: break;
460: case '|':
461: op = BooleanOperator.OR;
462: return;
463: case '+':
464: op = ArithmeticOperator.ADD;
465: break;
466: case '-':
467: op = ArithmeticOperator.SUBTRACT;
468: break;
469: case '*':
470: op = ArithmeticOperator.MULTIPLY;
471: break;
472: case '/':
473: op = ArithmeticOperator.DIVIDE;
474: break;
475: }
476: int opp = op.getRP();
477: if (opp < lp) {
478: parse(opp);
479: cc.push(op);
480: continue;
481: } else {
482: pushBack();
483: return;
484: }
485: }
486: } finally {
487: if (logger.isDebugEnabled())
488: logger.debug("Exit parse " + lp);
489: }
490: }
491:
492: private ConstraintPhrase parseConstraintPhrase() throws IOException {
493: if (!isWord(nextToken()))
494: throw unexpectedTokenException("Expected OperatingMode name");
495: ConstraintPhrase cp = new ConstraintPhrase(st.sval);
496: parseConstraintOpValue(cp);
497: return cp;
498: }
499:
500: private ConstraintOpValue parseConstraintOpValue(
501: ConstraintOpValue cov) throws IOException {
502: int token1 = nextToken();
503: if (cov == null)
504: cov = new ConstraintOpValue();
505:
506: switch (token1) {
507: case '<':
508: case '>':
509: case '=':
510: case '!':
511: int token2 = nextToken();
512: if (token2 == '=') {
513: switch (token1) {
514: case '<':
515: cov.setOperator(ConstraintOperator.LESSTHANOREQUAL);
516: break;
517: case '>':
518: cov
519: .setOperator(ConstraintOperator.GREATERTHANOREQUAL);
520: break;
521: case '=':
522: cov.setOperator(ConstraintOperator.EQUAL);
523: break;
524: case '!':
525: cov.setOperator(ConstraintOperator.NOTEQUAL);
526: break;
527: }
528: token1 = nextToken();
529: } else {
530: switch (token1) {
531: case '=':
532: cov.setOperator(ConstraintOperator.ASSIGN);
533: break;
534: case '<':
535: cov.setOperator(ConstraintOperator.LESSTHAN);
536: break;
537: case '>':
538: cov.setOperator(ConstraintOperator.GREATERTHAN);
539: break;
540: default:
541: throw unexpectedTokenException("Malformed ConstraintOperator");
542: }
543: token1 = token2;
544: }
545: break; // end of punctuation chars case
546: case '"':
547: case '\'':
548: case StreamTokenizer.TT_WORD:
549: if (st.sval.equalsIgnoreCase(ConstraintOperator.IN
550: .toString())) {
551: cov.setOperator(ConstraintOperator.IN);
552: token1 = nextToken();
553: break;
554: }
555: if (st.sval.equalsIgnoreCase("NOT")) {
556: if (isWord(nextToken())
557: && st.sval.equalsIgnoreCase("IN")) {
558: cov.setOperator(ConstraintOperator.NOTIN);
559: token1 = nextToken();
560: break;
561: }
562: }
563: default:
564: throw unexpectedTokenException("Missing ConstraintOperator");
565: }
566: if (isWord(token1)) {
567: cov.setAllowedValues(new OMCRangeList(parseRange(st.sval)));
568: } else if (token1 == StreamTokenizer.TT_NUMBER) {
569: cov.setAllowedValues(new OMCRangeList(
570: parseRange(new Double(st.nval))));
571: } else if (token1 == '{') {
572: cov.setAllowedValues(parseSet());
573: } else {
574: throw unexpectedTokenException("Expected range list");
575: }
576: return cov;
577: }
578:
579: private OMCRange parseRange(Comparable first) throws IOException {
580: Class elementClass = first.getClass();
581: int token = nextToken();
582: if (isWord(token)) {
583: boolean isTo = st.sval.equalsIgnoreCase("to");
584: boolean isThru = st.sval.equalsIgnoreCase("thru");
585: if (isTo || isThru) {
586: Comparable last;
587: token = nextToken();
588: if (isWord(token)) {
589: if (elementClass == Double.class) {
590: throw unexpectedTokenException("Number expected");
591: }
592: last = st.sval;
593: } else if (token == StreamTokenizer.TT_NUMBER) {
594: if (elementClass == String.class) {
595: throw unexpectedTokenException("String expected");
596: }
597: last = new Double(st.nval);
598: } else {
599: throw unexpectedTokenException("Expected "
600: + (elementClass == Double.class ? "number"
601: : "string"));
602: }
603: if (isTo) {
604: return new OMCToRange(first, last);
605: } else {
606: return new OMCThruRange(first, last);
607: }
608: }
609: }
610: pushBack();
611: return new OMCPoint(first);
612: }
613:
614: private OMCRangeList parseSet() throws IOException {
615: Class elementClass = null;
616: List values = new ArrayList();
617:
618: while (true) {
619: int token1 = nextToken();
620: if (isWord(token1)) {
621: if (elementClass == Double.class) {
622: throw unexpectedTokenException("Number expected");
623: } else if (elementClass == null) {
624: elementClass = String.class;
625: }
626: values.add(parseRange(st.sval));
627: } else if (token1 == StreamTokenizer.TT_NUMBER) {
628: if (elementClass == String.class) {
629: throw unexpectedTokenException("String expected");
630: } else if (elementClass == null) {
631: elementClass = Double.class;
632: }
633: values.add(parseRange(new Double(st.nval)));
634: } else if (token1 == ',') {
635: // ignore comma
636: } else if (token1 == '}') {
637: break;
638: } else {
639: throw unexpectedTokenException("Missing close brace");
640: }
641: }
642: return new OMCRangeList((OMCRange[]) values
643: .toArray(new OMCRange[values.size()]));
644: }
645:
646: private void pushBack() {
647: pushedBack = true;
648: }
649:
650: private boolean isWord(int token) {
651: return token == StreamTokenizer.TT_WORD || token == '"'
652: || token == '\'';
653: }
654:
655: private int nextToken() throws IOException {
656: int token;
657: String caller = null;
658: if (logger.isDebugEnabled())
659: caller = new Throwable().getStackTrace()[1].toString();
660: if (pushedBack) {
661: pushedBack = false;
662: token = st.ttype;
663: if (logger.isDebugEnabled())
664: logger.debug("nextToken from " + caller + ": repeat "
665: + tokenAsString());
666: } else {
667: token = st.nextToken();
668: if (logger.isDebugEnabled())
669: logger.debug("nextToken from " + caller + ": token "
670: + tokenAsString());
671: }
672: return token;
673: }
674:
675: private String tokenAsString() {
676: switch (st.ttype) {
677: case '"':
678: case '\'':
679: case StreamTokenizer.TT_WORD:
680: return st.sval;
681: case StreamTokenizer.TT_NUMBER:
682: return String.valueOf(st.nval);
683: case StreamTokenizer.TT_EOF:
684: return "<eof>";
685: default:
686: return String.valueOf((char) st.ttype);
687: }
688: }
689:
690: private IllegalArgumentException unexpectedTokenException(String mm) {
691: String line = "line " + st.lineno();
692: String mesg = tokenAsString();
693: IllegalArgumentException result = new IllegalArgumentException(
694: mm + " on " + line + " found " + mesg);
695: StackTraceElement[] trace = result.getStackTrace();
696: StackTraceElement[] callerTrace = new StackTraceElement[trace.length - 1];
697: System.arraycopy(trace, 1, callerTrace, 0, callerTrace.length);
698: result.setStackTrace(callerTrace);
699: return result;
700: }
701: }
|