001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.test;
018
019import java.io.BufferedReader;
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStreamReader;
023import java.net.URL;
024import java.util.StringTokenizer;
025
026import javax.xml.stream.XMLStreamException;
027
028import org.apache.commons.scxml2.Context;
029import org.apache.commons.scxml2.Evaluator;
030import org.apache.commons.scxml2.SCXMLExecutor;
031import org.apache.commons.scxml2.TriggerEvent;
032import org.apache.commons.scxml2.env.Tracer;
033import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker;
034import org.apache.commons.scxml2.io.SCXMLReader;
035import org.apache.commons.scxml2.io.SCXMLWriter;
036import org.apache.commons.scxml2.model.ModelException;
037import org.apache.commons.scxml2.model.SCXML;
038
039/**
040 * Utility methods used by command line SCXML execution, useful for
041 * debugging.
042 *
043 * The following expression languages are supported in SCXML documents:
044 * <ol>
045 *  <li>JEXL - Using Commons JEXL</li>
046 * </ol>
047 *
048 * @see org.apache.commons.scxml2.env.jexl
049 */
050public final class StandaloneUtils {
051
052    /**
053     * Command line utility method for executing the state machine defined
054     * using the SCXML document described by the specified URI and using
055     * the specified expression evaluator.
056     *
057     * @param uri The URI or filename of the SCXML document
058     * @param evaluator The expression evaluator for the expression language
059     *                  used in the specified SCXML document
060     *
061     * <p>RUNNING:</p>
062     * <ul>
063     *  <li>Enter a space-separated list of "events"</li>
064     *  <li>To quit, enter "quit"</li>
065     *  <li>To populate a variable in the current context,
066     *      type "name=value"</li>
067     *  <li>To reset state machine, enter "reset"</li>
068     * </ul>
069     */
070    public static void execute(final String uri, final Evaluator evaluator) {
071        try {
072            String documentURI = getCanonicalURI(uri);
073            Context rootCtx = evaluator.newContext(null);
074            Tracer trc = new Tracer();
075            SCXML doc = SCXMLReader.read(new URL(documentURI));
076            if (doc == null) {
077                System.err.println("The SCXML document " + uri
078                        + " can not be parsed!");
079                System.exit(-1);
080            }
081            System.out.println(SCXMLWriter.write(doc));
082            SCXMLExecutor exec = new SCXMLExecutor(evaluator, null, trc);
083            exec.setStateMachine(doc);
084            exec.addListener(doc, trc);
085            exec.registerInvokerClass("scxml", SimpleSCXMLInvoker.class);
086            exec.setRootContext(rootCtx);
087            exec.go();
088            BufferedReader br = new BufferedReader(new
089                InputStreamReader(System.in));
090            String event;
091            while ((event = br.readLine()) != null) {
092                event = event.trim();
093                if (event.equalsIgnoreCase("help") || event.equals("?")) {
094                    System.out.println("Enter a space-separated list of "
095                        + "events");
096                    System.out.println("To populate a variable in the "
097                        + "current context, type \"name=value\"");
098                    System.out.println("To quit, enter \"quit\"");
099                    System.out.println("To reset state machine, enter "
100                        + "\"reset\"");
101                } else if (event.equalsIgnoreCase("quit")) {
102                    break;
103                } else if (event.equalsIgnoreCase("reset")) {
104                    exec.reset();
105                } else if (event.indexOf('=') != -1) {
106                    int marker = event.indexOf('=');
107                    String name = event.substring(0, marker);
108                    String value = event.substring(marker + 1);
109                    rootCtx.setLocal(name, value);
110                    System.out.println("Set variable " + name + " to "
111                        + value);
112                } else if (event.trim().length() == 0
113                           || event.equalsIgnoreCase("null")) {
114                    TriggerEvent[] evts = {new TriggerEvent(null,
115                        TriggerEvent.SIGNAL_EVENT, null)};
116                    exec.triggerEvents(evts);
117                    if (exec.getStatus().isFinal()) {
118                        System.out.println("A final configuration reached.");
119                    }
120                } else {
121                    StringTokenizer st = new StringTokenizer(event);
122                    int tkns = st.countTokens();
123                    TriggerEvent[] evts = new TriggerEvent[tkns];
124                    for (int i = 0; i < tkns; i++) {
125                        evts[i] = new TriggerEvent(st.nextToken(),
126                                TriggerEvent.SIGNAL_EVENT, null);
127                    }
128                    exec.triggerEvents(evts);
129                    if (exec.getStatus().isFinal()) {
130                        System.out.println("A final configuration reached.");
131                    }
132                }
133            }
134        } catch (IOException e) {
135            e.printStackTrace();
136        } catch (ModelException e) {
137            e.printStackTrace();
138        } catch (XMLStreamException e) {
139                e.printStackTrace();
140        }
141    }
142
143    /**
144     * @param uri an absolute or relative URL
145     * @return java.lang.String canonical URL (absolute)
146     * @throws java.io.IOException if a relative URL can not be resolved
147     *         to a local file
148     */
149    private static String getCanonicalURI(final String uri)
150    throws IOException {
151        if (uri.toLowerCase().startsWith("http://")
152            || uri.toLowerCase().startsWith("file://")) {
153                return uri;
154        }
155        File in = new File(uri);
156        return "file:///" + in.getCanonicalPath();
157    }
158
159    /**
160     * Discourage instantiation since this is a utility class.
161     */
162    private StandaloneUtils() {
163        super();
164    }
165
166}
167