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: package org.cougaar.core.examples.mobility.ldm;
027:
028: import java.io.BufferedReader;
029: import java.io.Serializable;
030: import java.io.StringReader;
031: import java.util.ArrayList;
032: import java.util.HashMap;
033: import java.util.List;
034: import java.util.Map;
035: import org.cougaar.core.mobility.Ticket;
036: import org.cougaar.core.mts.MessageAddress;
037: import org.cougaar.core.util.UID;
038: import org.cougaar.core.util.UniqueObject;
039: import org.cougaar.util.CSVUtility;
040:
041: /**
042: * Package-private script parser.
043: * <p>
044: * Note that, at this time, the scripting language is rather
045: * limited. Future enhancements will include variables,
046: * conditionals, loops, math, etc.
047: * <p>
048: * Expects lines of text:<ul>
049: * <li>Empty lines are ignored</li>
050: * <li>Lines starting with "#" are ignored</li>
051: * <li>Lines starting with "label " are goto labels, and require
052: * a name (e.g. "label X").</li>
053: * <li>Lines starting with "goto " are goto commands, and require
054: * a name (e.g. "goto X"). They may branch forward or
055: * backwards in the script, but the label name must be
056: * contained in the script.
057: * </li>
058: * <li>Lines starting with "move " are mobility step commands,
059: * and are detailed below.</li>
060: * </ul>
061: * <p>
062: * "move" commands, also called "step"s, look like:
063: * <i>(example)</i><pre>
064: * move ActorAgent, ^0:30, +1:20, MobileAgent, OrigNode, DestNode, true
065: * </pre><br>
066: * and require 7 comma-separated parameters:<ol>
067: * <li><i>actor</i><br>
068: * The agent that should run the step. If an empty value is
069: * specified then the script runner's agent is assumed.
070: * </li>
071: * <li><i>[+^@]pauseTime</i> with ":" time dividers<br>
072: * The pause time before starting the move. See the notes
073: * below on time formats.
074: * <p>
075: * If no value or a negative value is specified, then there
076: * will be zero pause.
077: * </li>
078: * <li><i>[+^@]timeoutTime</i> with ":" time dividers<br>
079: * The timeout time for the move, relative to before the
080: * above (optional) pause time. See the notes below on
081: * time formats.
082: * <p>
083: * If no value or a negative value is specified, then there
084: * will be no timeout.
085: * </li>
086: * <li><i>mobileAgent</i><br>
087: * Agent that should be moved, or the script runner's agent
088: * if an empty value is specified.
089: * </li>
090: * <li><i>origNode</i><br>
091: * Node that the mobile agent should be on when the move
092: * is started, or an empty value if any node is allowed.
093: * </li>
094: * <li><i>destNode</i><br>
095: * Node that the mobile agent should be on when the move
096: * has completed, or an empty value if the agent should
097: * move to its current node. See the "forceRestart"
098: * option below.
099: * </li>
100: * <li><i>forceRestart</i><br>
101: * Specify "true" if the mobile agent should be fully
102: * restarted, even if the destination node is the same
103: * as that agent's starting node.
104: * </li>
105: * </ol>
106: * <p>
107: * Time is formatted as <i>[+^@]time</i>, where the
108: * <i>time</i> is further separated with <i>:</i>
109: * and <i>.</i>characters:<ol>
110: * <li>If <i>+</i> is specified then the time is relative
111: * to the runtime start of the current step. This can
112: * be used to create a sequence of moves where you're
113: * not counting the time to do the agent move.</li>
114: * <li>If <i>^</i> is specified then the time is relative
115: * to the runtime start of the prior step excluding the
116: * pause time (same as + on the first step). This can
117: * be used to create a sequence of moves where you want
118: * to count the move as part of the pause time.</li>
119: * <li>If <i>@</i> is specified then the time is relative
120: * to the start time of first step in the script (same as
121: * + on the first step)</li>
122: * <li>If no <i>[+^@]</i> modifier is used an absolute
123: * system time in millseconds since Jan 1st 1970 is assumed.
124: * <li>If no ":" or "." is present in the time, then
125: * milliseconds is assumed.</li>
126: * <li>If one ":" is present and no ".", then the time is
127: * the addition of the value before the ":" as minutes, and
128: * the value after the ":" as milliseconds, i.e.:<pre>
129: * (v[1] + 1000 * (v[0]))
130: * </pre></li>
131: * <li>If one ":" and one "." is present, then the formula
132: * yields minutes:seconds.millis<pre>
133: * (v[3] + 1000 * (v[1] * 60 * (v[0])))
134: * </pre></li>
135: * </ol>
136: * <p>
137: * Ugly example script:<pre>
138: *
139: * # my comment
140: * # pause until it's 20 seconds after the script launch
141: * # move agent A from node X to node Y
142: * # no timeout for this move
143: * move , @0:20, , A, X, Y,
144: *
145: * # pause until it's 60 seconds after script launch
146: * # move agent A from whichever node it happens to be to node Z
147: * # timeout if the move doesn't complete within
148: * # 30 seconds (which is equivalent to @(60+30) == @90
149: * move , @0:60, +:30, A, , Z,
150: *
151: * # pause until at least 50 seconds have passed since the
152: * # prior move was started (excluding the above 60 second
153: * # pause). If the above agent move took 10 seconds, this
154: * # would be equivalent to @(60+50-10) == @100
155: * # have agent B request that agent A move to node X
156: * # timeout if the move doesn't complete before it's
157: * # 1 minute 5 seconds 100 milliseconds after the script
158: * # launch
159: * move B, ^0:50, @1:5.1, A, , X,
160: *
161: * # pause for 30 seconds
162: * # move A to X
163: * # no timeout for this move
164: * # force a restart even if agent A is already on node X
165: * move , +:30, , A, , X, true
166: *
167: * # begin an infinite loop
168: * label foo
169: *
170: * # pause for 30 seconds
171: * # move B from X to Y
172: * # timeout after 60 seconds
173: * move , +:30, +:60, B, X, Y,
174: *
175: * # pause until it's 1 minute 20 seconds after the prior
176: * # started its move (after its 30 second pause)
177: * # move B back
178: * # timeout after 1 minute
179: * move , ^1:20, +1:, B, Y, X,
180: *
181: * # note that we can't use "@" times in a loop
182: *
183: * # keep moving B back and forth forever
184: * goto foo
185: *
186: * # never reach here!
187: *
188: * </pre>
189: */
190: class ScriptParser {
191:
192: public static List parse(String text) {
193:
194: // not pretty!
195:
196: StringReader sr;
197: BufferedReader br;
198: try {
199: sr = new StringReader(text);
200: br = new BufferedReader(sr);
201: } catch (Exception e) {
202: throw new RuntimeException("Unable to read text", e);
203: }
204:
205: List ret = new ArrayList();
206: int maxGotoIdx = -1;
207: int maxGotoLine = -1;
208: boolean hasNamedGotos = false;
209: Map labels = new HashMap(5);
210: for (int i = 0;; i++) {
211: String origLine = null;
212: try {
213: origLine = br.readLine();
214: String line = origLine;
215: if (line == null) {
216: break;
217: }
218: line = line.trim();
219: if ((line.length() == 0) || (line.charAt(0) == '#')) {
220: continue;
221: }
222: if (line.startsWith("label ")) {
223: String label = line.substring(6).trim();
224: if (label.length() == 0) {
225: throw new RuntimeException("Empty label name");
226: }
227: Object o = labels.put(label,
228: new Integer(ret.size()));
229: if (o != null) {
230: throw new RuntimeException("Label " + label
231: + " already defined");
232: }
233: ScriptLabel sl = new ScriptLabel(label);
234: ret.add(sl);
235: continue;
236: }
237: if (line.startsWith("goto ")) {
238: String name = line.substring(5).trim();
239: if (name.length() == 0) {
240: throw new RuntimeException("Empty goto name");
241: }
242: Object o = labels.get(name);
243: if (o != null) {
244: int idx = ((Integer) o).intValue();
245: ret.add(new ScriptGoto(name, idx));
246: } else {
247: hasNamedGotos = true;
248: // use a temporary entry, fix after main parse loop
249: ret.add(new NamedGoto(name, ret.size()));
250: }
251: continue;
252: }
253: if (line.startsWith("move ")) {
254: line = line.substring(5);
255: line = line.trim();
256: List l = CSVUtility.parseToList(line);
257: ScriptStep ss = parseStep(l);
258: ret.add(ss);
259: continue;
260: }
261: throw new RuntimeException(
262: "Unknown command, not (label, goto, move)");
263: } catch (Exception e) {
264: throw new RuntimeException("Invalid line[" + i + "] "
265: + origLine, e);
266: }
267: }
268: if (hasNamedGotos) {
269: // replace all NamedGotos with resolved ScriptGotos
270: for (int j = 0, nj = ret.size(); j < nj; j++) {
271: Object oj = ret.get(j);
272: if (oj instanceof NamedGoto) {
273: NamedGoto ng = (NamedGoto) oj;
274: String name = ng.getName();
275: Object lb = labels.get(name);
276: if (lb == null) {
277: throw new RuntimeException("Unknown goto "
278: + name + " at line " + ng.getIdx());
279: } else {
280: int idx = ((Integer) lb).intValue();
281: ret.set(j, new ScriptGoto(name, idx));
282: }
283: }
284: }
285: }
286: return ret;
287: }
288:
289: private static ScriptStep parseStep(List l) {
290: // expecting:
291: // {A, P, T, M, O, N, F}
292: // where:
293: // A := (actorAgent)
294: // P := ([@+]pauseTime)
295: // T := ([@+]timeoutTime)
296: // M := (mobileAgent)
297: // O := (origNode)
298: // D := (destNode)
299: // F := (forceRestart)
300: if (l.size() != 7) {
301: throw new RuntimeException("Expecting a step with 7 (not "
302: + l.size() + ")" + " comma-separated elements");
303: }
304:
305: int flags = 0;
306: String s;
307:
308: // refactor me!
309:
310: MessageAddress actorAgent;
311: s = (String) l.get(0);
312: if ((s != null) && (s.length() > 0)) {
313: // FIXME RelayLP
314: actorAgent = MessageAddress.getMessageAddress(s);
315: } else {
316: actorAgent = null;
317: }
318:
319: long pauseTime;
320: s = (String) l.get(1);
321: if ((s != null) && (s.length() > 0)) {
322: char ch = s.charAt(0);
323: if (ch == '@') {
324: flags |= ScriptStep.REL_PAUSE;
325: s = s.substring(1);
326: } else if (ch == '+') {
327: flags |= ScriptStep.ADD_PAUSE;
328: s = s.substring(1);
329: } else if (ch == '^') {
330: flags |= ScriptStep.PRI_PAUSE;
331: s = s.substring(1);
332: }
333: pauseTime = parseTime(s);
334: } else {
335: pauseTime = -1;
336: }
337:
338: long timeoutTime;
339: s = (String) l.get(2);
340: if ((s != null) && (s.length() > 0)) {
341: char ch = s.charAt(0);
342: if (ch == '@') {
343: flags |= ScriptStep.REL_TIMEOUT;
344: s = s.substring(1);
345: } else if (ch == '+') {
346: flags |= ScriptStep.ADD_TIMEOUT;
347: s = s.substring(1);
348: } else if (ch == '^') {
349: flags |= ScriptStep.PRI_TIMEOUT;
350: s = s.substring(1);
351: }
352: timeoutTime = parseTime(s);
353: } else {
354: timeoutTime = -1;
355: }
356:
357: MessageAddress mobileAgent;
358: s = (String) l.get(3);
359: if ((s != null) && (s.length() > 0)) {
360: // FIXME RelayLP
361: mobileAgent = MessageAddress.getMessageAddress(s);
362: } else {
363: mobileAgent = null;
364: }
365:
366: MessageAddress origNode;
367: s = (String) l.get(4);
368: if ((s != null) && (s.length() > 0)) {
369: origNode = MessageAddress.getMessageAddress(s);
370: } else {
371: origNode = null;
372: }
373:
374: MessageAddress destNode;
375: s = (String) l.get(5);
376: if ((s != null) && (s.length() > 0)) {
377: destNode = MessageAddress.getMessageAddress(s);
378: } else {
379: destNode = null;
380: }
381:
382: boolean forceRestart;
383: s = (String) l.get(6);
384: forceRestart = "true".equals(s);
385:
386: Ticket ticket = new Ticket(null, mobileAgent, origNode,
387: destNode, forceRestart);
388:
389: StepOptions opts = new StepOptions(null, null, actorAgent,
390: ticket, pauseTime, timeoutTime);
391:
392: ScriptStep ret = new ScriptStep(flags, opts);
393:
394: return ret;
395: }
396:
397: private static long parseTime(String s) {
398: int i = s.lastIndexOf(':');
399: if (i < 0) {
400: // exact millis
401: return Long.parseLong(s);
402: }
403: long ret = 0;
404: if (i > 0) {
405: // minutes
406: long t = Long.parseLong(s.substring(0, i));
407: t *= 60 * 1000;
408: ret += t;
409: }
410: s = s.substring(i + 1);
411: if (s.length() == 0) {
412: // minutes only
413: return ret;
414: }
415: i = s.indexOf('.');
416: if (i >= 0) {
417: // millis
418: if ((i + 1) < s.length()) {
419: ret += Long.parseLong(s.substring(i + 1));
420: }
421: s = s.substring(0, i);
422: }
423: if (s.length() > 0) {
424: // seconds
425: long t = Long.parseLong(s);
426: t *= 1000;
427: ret += t;
428: }
429: return ret;
430: }
431:
432: private static class NamedGoto {
433: private final String name;
434: private final int origIdx;
435:
436: public NamedGoto(String name, int origIdx) {
437: this .name = name;
438: this .origIdx = origIdx;
439: }
440:
441: public String getName() {
442: return name;
443: }
444:
445: public int getIdx() {
446: return origIdx;
447: }
448: }
449: }
|