View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.bot.sposh;
2   
3   import java.io.IOException;
4   import java.io.Reader;
5   import java.util.List;
6   
7   import javax.script.Invocable;
8   import javax.script.ScriptContext;
9   import javax.script.ScriptEngine;
10  import javax.script.ScriptEngineManager;
11  import javax.script.ScriptException;
12  
13  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
14  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004BotLogicController;
15  
16  /**
17   * This is class for the agents that are using the ScriptEngine. ScriptEngine is
18   * a part of Java API that allows execution of scripting file using integrated engine.
19   * <p>
20   * This basically allows us to create a agent with logic implemented in some
21   * scripting language (like JavaScript or Python). Beware, Java has currently
22   * implemented  only one engine and that is JavaScript, for others it is
23   * necessary to provide custom binding library for the language (like jython).
24   * <p>
25   * The script that will be executed shoudl contain all the normal functions
26   * that are expected, i.e.:
27   * <ul>
28   *   <li>doLogic</li>
29   *   <li>prePrepareAgent</li>
30   *   <li>postPrepareAgent</li>
31   *   <li>shutdownAgent</li>
32   * </ul>
33   * <p>
34   * You have to specify the file to load and engine to bind before
35   * starting the agent.
36   *
37   * Subclasses has to implement {@link StreamScriptedAgent.getScriptFile()} to
38   * specify the script. This is done mostly because of google Guice.
39   *
40   * Also, if subclass is utilizing some script that is not available as default, 
41   * it should override {@link StreamScriptedAgent.engineIsGoingToBeBinded()} to set it up.
42   *
43   * TODO: this class is rather ugly, it deserves some clean up.
44   * <p>
45   * It should go approx. like this
46   *  * Create a new instance
47   *  * Create ScriptEngineManager
48   *  * call engineIsGoingToBeBinded()
49   *  * get engine from manager based on extension or something
50   *
51   * How to get data:
52   *
53   * get file
54   * get stream from file
55   *
56   * What can be done:
57   * <ol>
58   *   <li>Override getScriptFile, in such case default get stream from file will be used.
59   *       Engine will be created based on extension of file.
60   *   </li>
61   *   <li>Override getScriptStream, in that case getScriptFile won't be used, but
62   *       user has to specify the type of engine.and
63   *
64   *   </li>
65   * </or>
66   *
67   * @author Honza
68   */
69  
70  public abstract class StreamScriptLogic extends UT2004BotLogicController {
71  
72      protected ScriptEngineManager scriptEngineManager;
73      /**
74       * ScriptEngine which contains the file with logic.
75       */
76      protected ScriptEngine engine = null;
77      /**
78       * Same engine as in {@link engine}, but recasted as Invocable, allowing us to call methods.
79       */
80      protected Invocable invocableEngine = null;
81      
82      /**
83       * Constructor which is needed when you have to initialize the
84       * environment of the scripting language.
85       * <p>
86       * Currently used by SPOSHBot.
87       */
88      @Override
89      public void initializeController(UT2004Bot bot) {
90      	super.initializeController(bot);
91          // first create proper ScriptEngine
92          scriptEngineManager = createScriptEngineManager();
93          this.engine = createScriptEngine(scriptEngineManager);
94          this.invocableEngine = (Invocable) this.engine;
95          this.engineBinded();
96  
97          // and than load up the script.
98          try {
99              evalStream(this.getScriptStream());
100         } catch (Exception ex) {
101             ex.printStackTrace();
102             throw new ScriptedAgentException("Unable to evaluate script", ex);
103         }
104         scriptBinded();
105     }
106     
107     /**
108      * Return correct ScriptEngine for script this bot is going to run.
109      * <p>
110      * Because this class is using stream instead of file we don't know what
111      * is scripting language of processed script. That is what this function is for.
112      *
113      * @param manager {@link ScriptEngineManager} that is used to manage engines. 
114      * @return correct {@link ScriptEngine} for this agent. 
115      */
116     protected abstract ScriptEngine createScriptEngine(ScriptEngineManager manager);
117 
118     /**
119      * Return stream for script that this class should execute.
120      * @return input stream containing script. Not null.
121      */
122     protected abstract Reader getScriptStream() throws IOException;
123 
124     /**
125      * This method is called after the script is evaluated.
126      * To be used by developer, for some adjustments or such.
127      */
128     protected abstract void scriptBinded();
129 
130     /**
131      * Creates script engine manager that looks for engines in classpath and
132      * also in $POGAMUT_PLATFORM/scriptEngines.
133      */
134     private static ScriptEngineManager createScriptEngineManager() {
135         return new ScriptEngineManager();
136     }
137 
138     /**
139      * This method is called when the engine is binded.
140      */
141     protected abstract void engineBinded();
142 
143     /**
144      * Sets attribute to the global scope of the engine.
145      * TODO: same as engine.put(name, attribute) ? Learn more about scopes.
146      * @param name
147      * @param attribute
148      */
149     final protected void setAttribute(String name, Object attribute) {
150         ScriptContext context = this.engine.getContext();
151         List<Integer> scopes = context.getScopes();
152         context.setAttribute(name, attribute, scopes.get(0));
153     }
154 
155     /**
156      * Evaluates the stream of the script.
157      *
158      * @param is
159      * @return true if eval succeeded
160      * @throws ScriptedAgentException
161      */
162     final protected boolean evalStream(Reader reader) throws ScriptedAgentException {
163         try {
164             engine.eval(reader);
165             return true;
166         } catch (ScriptException e) {
167             getLog().severe("Script error -> " + e.getMessage());
168             throw new ScriptedAgentException("Script error -> " + e.getMessage(), e);
169         }
170     }
171 
172     /**
173      * Calls function without parameters from the ScriptEngine.
174      *
175      * @param name
176      * @return true if everything is ok
177      * @throws ScriptedAgentException
178      */
179     final protected boolean callFunction(String name) throws ScriptedAgentException {
180         try {
181             this.invocableEngine.invokeFunction(name, (Object[]) null);
182         } catch (ScriptException e) {
183             String msg = "Script exception -> " + e.getMessage();
184             getLog().severe(msg);
185 
186             throw new ScriptedAgentException(msg, e);
187         } catch (NoSuchMethodException e) {
188             String msg = "Method '" + name + "' uninmplemented by scripts.";
189             getLog().severe(msg);
190 
191             throw new ScriptedAgentException(msg, e);
192         }
193         return true;
194     }
195 
196     public UT2004Bot getBot() {
197         return bot;
198     }
199     
200 }