001/* An actor whose execution is specified by Java code. 002 003Copyright (c) 2014 The Regents of the University of California. 004All rights reserved. 005Permission is hereby granted, without written agreement and without 006license or royalty fees, to use, copy, modify, and distribute this 007software and its documentation for any purpose, provided that the above 008copyright notice and the following two paragraphs appear in all copies 009of this software. 010 011IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015SUCH DAMAGE. 016 017THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022ENHANCEMENTS, OR MODIFICATIONS. 023 024*/ 025package org.kepler.scriptengine; 026 027import java.lang.reflect.Field; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import javax.script.ScriptContext; 032import javax.script.ScriptEngine; 033import javax.script.ScriptException; 034 035import org.kepler.scriptengine.java.JavaScriptEngine; 036import org.kepler.scriptengine.java.JavaScriptEngineFactory; 037 038import ptolemy.data.StringToken; 039import ptolemy.kernel.CompositeEntity; 040import ptolemy.kernel.util.IllegalActionException; 041import ptolemy.kernel.util.NameDuplicationException; 042import ptolemy.kernel.util.Workspace; 043 044/** An actor whose execution is defined by Java code. 045 * The code can be edited by double-clicking on the actor. 046 * <p>There are several differences between this actor and 047 * the BeanShell Java actor. This actor requires: 048 * <ul> 049 * <li>A Java compiler.</li> 050 * <li>All the referenced classes must be imported.</li> 051 * <li>All ports and parameters must be explicitly declared 052 * as fields in the script.</li> 053 * </ul> 054 * There are several Java features supported by this actor but 055 * not the BeanShell actor: 056 * <ul> 057 * <li>Annotations</li> 058 * <li>Generics</li> 059 * </ul> 060 * </p> 061 * <p>The following example defines an actor in Java that 062 * computes factorials:</p> 063 * 064<pre> 0651. import ptolemy.actor.*; 0662. import ptolemy.data.*; 0673. import ptolemy.kernel.util.IllegalActionException; 0684. 0695. import org.kepler.scriptengine.Java; 0706. 0717. public class Actor { 0728. public void fire() throws IllegalActionException { 0739. 07410. int val = ((IntToken)in.get(0)).intValue(); 07511. 07612. if(val < 0) { 07713. actor.error("Input must be non-negative."); 07814. } 07915. 08016. int total = 1; 08117. while(val > 1) { 08218. total *= val; 08319. val--; 08420. } 08521. 08622. out.broadcast(new IntToken(total)); 08723. } 08824. 08925. public TypedIOPort in; 09026. public TypedIOPort out; 09127. public Java actor; 09228.} 093</pre> 094 * 095 * <p>Lines 1-3 import the Java classes used by the actor. Line 7 defines the 096 * Actor object. Lines 8-23 define the fire() method of the actor; this 097 * method is called each time this actor executes in the workflow. Line 10 098 * reads an integer from the input port called "in". A port or parameter 099 * called "foo" can be accessed in the Java code by simply using "foo". 100 * Lines 16-20 compute the factorial of the integer read from the port. 101 * Line 22 writes the result to the output port called "out". 102 * 103 * @author Daniel Crawl 104 * @version $Id: Java.java 34143 2015-10-28 21:16:33Z crawl $ 105 */ 106public class Java extends ScriptEngineActor { 107 108 /** Create a new Java in the specified workspace. */ 109 public Java(Workspace workspace) { 110 super(workspace); 111 } 112 113 /** Create a new Java with the given container and name. */ 114 public Java(CompositeEntity container, String name) 115 throws IllegalActionException, NameDuplicationException { 116 super(container, name); 117 118 _invokeMethodWithReflection = true; 119 120 _editorFactory.syntaxStyle.setExpression("text/java"); 121 122 language.setToken("java"); 123 124 script.setExpression( 125 "import org.kepler.scriptengine.Java;\n" + 126 "\n" + 127 "public class Actor {\n" + 128 " public void fire() {\n" + 129 " System.out.println(\"in fire\");\n" + 130 " }\n" + 131 "}\n"); 132 } 133 134 /** Clone the actor into the specified workspace. */ 135 @Override 136 public Object clone(Workspace workspace) throws CloneNotSupportedException { 137 final Java newObject = (Java) super.clone(workspace); 138 139 // register Java ScriptEngine factory with in the ScriptEngineManager 140 // since it is not loaded in a jar. 141 JavaScriptEngineFactory factory = new JavaScriptEngineFactory(); 142 newObject._manager.registerEngineName(factory.getEngineName(), factory); 143 144 return newObject; 145 } 146 147 /** Evaluate the script. This overrides the parent class to set the file name 148 * based on the actor name and set the parent class loader. 149 * @return Returns the object resulting from evaluating the script. 150 */ 151 @Override 152 protected Object _evaluateScript() throws ScriptException, IllegalActionException { 153 154 String scriptStr = script.getExpression(); 155 156 // find the name of the class since we need to give the compiler a 157 // file name (even though a file is not actually created in the 158 // file system). 159 160 // find first "public class Name" 161 Matcher matcher = CLASS_DECL_NAME.matcher(scriptStr); 162 if(!matcher.find()) { 163 throw new IllegalActionException(this, "Could not find actor class name."); 164 } 165 166 String className = matcher.group(1); 167 actorClassName.setToken(new StringToken(className)); 168 169 // set the file name 170 _engine.getContext().setAttribute(ScriptEngine.FILENAME, 171 className + ".java", 172 ScriptContext.ENGINE_SCOPE); 173 174 // set the parent class loader. this is necessary so that we can set fields 175 // in the actor class, e.g., set the ports and parameters. 176 _engine.getContext().setAttribute(JavaScriptEngine.PARENTLOADER, 177 getClass().getClassLoader(), 178 ScriptContext.ENGINE_SCOPE); 179 180 // finally evaluate the script 181 return _engine.eval(scriptStr); 182 } 183 184 /////////////////////////////////////////////////////////////////// 185 //// protected methods //// 186 187 /** Create an instance of the actor object in the script. 188 * @return the actor instance. 189 */ 190 @Override 191 protected Object _createActorInstance(String actorClassNameStr) throws ScriptException { 192 try { 193 return ((Class<?>)_evaluatedScriptObject).newInstance(); 194 } catch (InstantiationException | IllegalAccessException e) { 195 throw new ScriptException("Error instantiating class: " + e.getMessage()); 196 } 197 } 198 199 /** Put the given object to the actor instance in the script. 200 * @param name the name of the object to put. 201 * @param object the object to put. 202 */ 203 @Override 204 protected void _putObjectToActorInstance(String name, Object object) throws ScriptException { 205 206 // see if the actor class has this field 207 Field field = null; 208 try { 209 field = _actorObject.getClass().getField(name); 210 } catch(Throwable e) { 211 // do nothing 212 } 213 214 if(field == null) { 215 System.err.println("WARNING: actor class does not have field '" + 216 name + "' of type " + object.getClass()); 217 //throw new ScriptException("Actor class does not have field '" + name + "'."); 218 } else { 219 /* 220 try { 221 222 Object fieldObject = field.get(_actorObject); 223 if(Typeable.class.isAssignableFrom(fieldObject.getClass())) { 224 //System.out.println("field " + name + " is typeable."); 225 Typeable typeable = (Typeable) fieldObject; 226 //System.out.println("type = " + typeable.getType()); 227 //System.out.println("typeTerm = " + typeable.getTypeTerm()); 228 ((Typeable)object).setTypeSameAs(typeable); 229 } 230 } catch (IllegalArgumentException | IllegalAccessException e) { 231 MessageHandler.error("Error getting type", e); 232 } 233 */ 234 235 236 try { 237 // set the field in the actor instance. 238 field.set(_actorObject, object); 239 } catch (Exception e) { 240 throw new ScriptException("Error setting port/parameter " + name + ": " + e.getMessage()); 241 } 242 } 243 } 244 245 /** Register the Java ScriptEngine factory with in the 246 * ScriptEngineManager since it is not loaded in a jar. 247 */ 248 @Override 249 protected void _registerEngineFactory() { 250 super._registerEngineFactory(); 251 JavaScriptEngineFactory factory = new JavaScriptEngineFactory(); 252 _manager.registerEngineName(factory.getEngineName(), factory); 253 } 254 255 /** The regex pattern to match the class name. */ 256 private final static Pattern CLASS_DECL_NAME = Pattern.compile("public class (\\w+)"); 257 258}