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}