001/* An actor whose execution is specified by a JRuby script.
002
003Copyright (c) 2013 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 javax.script.ScriptException;
028
029import ptolemy.kernel.CompositeEntity;
030import ptolemy.kernel.util.IllegalActionException;
031import ptolemy.kernel.util.NameDuplicationException;
032import ptolemy.kernel.util.Workspace;
033
034/** An actor whose execution is defined by a JRuby script.
035 *  The script can be edited by double-clicking on the actor.   
036 *  
037 *  <p><b>NOTE:</b>The JRuby jar must be downloaded separately
038 *  since it is released with the GPL license.</p>
039 *  
040 *  <p>The following example defines an actor in JRuby that
041 *  computes factorials:</p>
042<pre>
0431.  include Java
0442. 
0453.  import Java::ptolemy.data.IntToken
0464. 
0475.  class Actor
0486.      # create accessor methods for self field
0497.      # the java actor will be assigned to this field.
0508.      attr_accessor :self
0519. 
05210.     def fire
05311.         # read the input value from the port "input"
05412.         val = @self.getPort('input').get(0).intValue
05513. 
05614.         # calculate the factorial
05715.         total = 1
05816.         if val < 0
05917.             @self.error("Input must be greater than or equal to 0")
06018.         else
06119.             while val > 1 do
06220.                 total *= val
06321.                 val -= 1
06422.             end
06523.        end
06624. 
06725.         # write the factorial to the port "output"
06826.         @self.getPort('output').broadcast(IntToken.new(total))
06927.     end
07028. end
071</pre>
072 *
073 *  <p>Line 1 includes the module for the JVM, and line 3 imports the Java
074 *  class used by the actor. Lines 5-28 define the Actor object; the actor
075 *  object must be named "Actor" in JRuby scripts. Line 8 specifies that
076 *  accessor methods should be created for a field called "self". This
077 *  field is a reference to the Java object of this actor, and can be used
078 *  to access ports and parameters. Lines 10-27 define the fire() method of
079 *  the actor, which is called each time this actor executes in the workflow.
080 *  Line 12 reads an integer from the input port called "input". A port or
081 *  parameter called "foo" can be accessed in the JRuby script by using
082 *  @self.getPort("foo") or @self.attribute("foo"), respectively. Lines 15-23
083 *  compute the factorial of the input number, and line 26 writes the result
084 *  to the output port called "output".</p>
085 *  
086 *  @author Daniel Crawl
087 *  @version $Id: JRuby.java 33884 2015-09-11 18:07:23Z crawl $
088 */
089public class JRuby extends ScriptEngineActor {
090
091    /** Construct a new JRuby for a specified workspace. */
092    public JRuby(Workspace workspace) {
093        super(workspace);
094    }
095
096    /** Construct a new JRuby with the given container and name. */
097    public JRuby(CompositeEntity container, String name)
098            throws IllegalActionException, NameDuplicationException {
099        super(container, name);
100        
101        // this property must be set to persistent so that assignment
102        // statements in the script are persistent across calls to
103        // engine.eval() and engine.put().
104        System.setProperty("org.jruby.embed.localvariable.behavior", "persistent");
105
106        try {
107            language.setToken("ruby");
108        } catch(IllegalActionException e) {
109            // an exception is probably due to the jruby jar missing
110            throw new IllegalActionException(this, e,
111                    "Error loading the script engine for JRuby. This can happen if\n" +
112                    "the JRuby jar is not present. This jar is not included with\n" +
113                    "Kepler due to licensing and must be downloaded separately.\n" +
114                    "To use the JRuby actor: download the jar, either add it to\n" +
115                    "your $CLASSPATH environment variable or copy it into\n" +
116                    "kepler.modules/actors/lib/jar/, and restart Kepler.");
117        }
118        
119        _editorFactory.syntaxStyle.setExpression("text/ruby");
120
121        // a simple actor that prints when fire() is called
122        script.setExpression("class Actor\n" +
123                        "  attr_accessor :self\n" +
124                "  def fire\n" +
125                "     puts \"in fire\"\n" +
126                "  end\n" +
127                "end\n");
128    }
129
130    ///////////////////////////////////////////////////////////////////
131    ////                         protected methods                 ////
132
133    /** Create an instance of the actor object in the script.
134     *  @return the actor instance.
135     */
136    @Override
137    protected Object _createActorInstance(String actorClassNameStr) throws ScriptException {
138        _engine.eval(_ACTOR_INSTANCE_NAME + " = " + actorClassNameStr + ".new");
139        return _engine.get(_ACTOR_INSTANCE_NAME);
140    }
141    
142    /** Put the given object to the actor instance in the script.
143     *  @param name the name of the object to put.
144     *  @param object the object to put.
145     */
146    @Override
147    protected void _putObjectToActorInstance(String name, Object object) throws ScriptException {
148        String globalName = "_yyy_" + name;
149        _engine.put(globalName, this);
150        _engine.eval(_ACTOR_INSTANCE_NAME + ".self = " + globalName);
151    }
152}