org.jruby.embed.ScriptingContainer Maven / Gradle / Ivy
/**
* **** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2009-2013 Yoko Harada
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
* **** END LICENSE BLOCK *****
*/
package org.jruby.embed;
import java.io.Closeable;
import java.io.File;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jnr.posix.util.Platform;
import org.jruby.CompatVersion;
import org.jruby.Profile;
import org.jruby.Ruby;
import org.jruby.RubyGlobal.InputGlobalVariable;
import org.jruby.RubyGlobal.OutputGlobalVariable;
import org.jruby.RubyIO;
import org.jruby.RubyInstanceConfig.CompileMode;
import org.jruby.RubyInstanceConfig.LoadServiceCreator;
import org.jruby.RubyInstanceConfig.ProfilingMode;
import org.jruby.embed.internal.BiVariableMap;
import org.jruby.embed.internal.ConcurrentLocalContextProvider;
import org.jruby.embed.internal.EmbedRubyInterfaceAdapterImpl;
import org.jruby.embed.internal.EmbedRubyObjectAdapterImpl;
import org.jruby.embed.internal.EmbedRubyRuntimeAdapterImpl;
import org.jruby.embed.internal.LocalContextProvider;
import org.jruby.embed.internal.SingleThreadLocalContextProvider;
import org.jruby.embed.internal.SingletonLocalContextProvider;
import org.jruby.embed.internal.ThreadSafeLocalContextProvider;
import org.jruby.embed.io.ReaderInputStream;
import org.jruby.embed.io.WriterOutputStream;
import org.jruby.embed.util.SystemPropertyCatcher;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.GlobalVariable;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.Block;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.profile.builtin.ProfileOutput;
import org.jruby.util.ClassCache;
import org.jruby.util.JRubyClassLoader;
import org.jruby.util.KCode;
import org.jruby.util.cli.OutputStrings;
import org.jruby.util.cli.Options;
/**
* ScriptingContainer provides various methods and resources that are useful
* for embedding Ruby in Java. Using this class, users can run Ruby scripts from
* Java programs easily. Also, users can use methods defined or implemented by Ruby.
*
* ScriptingContainer allows users to set various configuration parameters.
* Some of them are per-container properties, while others are per-evaluation attributes.
* For example, a local context scope, local variable behavior, load paths are
* per-container properties. Please see {@link PropertyName} and {@link AttributeName}
* for more details. Be aware that the per-container properties should be set prior to
* get Ruby runtime being instantiated; otherwise, default values are applied to.
*
* ScriptingContainer delays Ruby runtime initialization as much as possible to
* improve startup time. When values are put into the ScriptingContainer, or runScriptlet
* method gets run the runtime is created internally. However, the default, singleton
* local context scope behave slightly different. If a Ruby runtime has been already
* instantiated by another ScriptingContainer, application, etc, the same runtime
* will be re-used.
*
* Below are examples.
*
* The first Example is a very simple Hello World. After initializing a ScriptingContainer,
* a Ruby script, puts "Hello World!", runs and produces "Hello World!."
*
* Example 1:
* {@code
* ScriptingContainer container = new ScriptingContainer();
* container.runScriptlet("puts \"Hello World!\"");
* }
* Produces:
* Hello World!
*
*
* The second example shows how to share variables between Java and Ruby.
* In this example, a local variable {@code "x"} is shared. To make this happen,
* a local variable behavior should be transient or persistent.
* As for JSR223 JRuby engine, set these types using a System property,
* org.jruby.embed.localvariable.behavior.
* If the local variable behavior is one of transient or persistent,
* Ruby's local, instance, global variables and constants are available to share
* between Java and Ruby. (A class variable sharing does not work on current version)
* Thus, {@code "x"} in Java is also {@code "x"} in Ruby.
*
*
* Example 2:
* {@code
* ScriptingContainer container = new ScriptingContainer();
* container.put("x", 12345);
* container.runScriptlet("puts x.to_s(2)");
* }
* Produces:
* 11000000111001
*
*
* The third examples shows how to keep local variables across multiple evaluations.
* This feature simulates BSF engine for JRuby. In terms of Ruby semantics,
* local variables should not survive after the evaluation has completed. Thus,
* this behavior is optional, and users need to specify LocalVariableBehavior.PERSISTENT
* when the container is instantiated.
*
*
* Example 3:
* {@code
* ScriptingContainer container = new ScriptingContainer(LocalVariableBehavior.PERSISTENT);
* container.runScriptlet("p=9.0");
* container.runScriptlet("q = Math.sqrt p");
* container.runScriptlet("puts \"square root of #{p} is #{q}\"");
* System.out.println("Ruby used values: p = " + container.get("p") + ", q = " + container.get("q"));
* }
* Produces:
* square root of 9.0 is 3.0
* Ruby used values: p = 9.0, q = 3.0
*
*
* Also, ScriptingContainer provides better i18n support. For example,
* Unicode Escape Sequence can be included in Ruby scripts.
*
* In addition, ScriptingContainer supports a parse-once-eval-many-times feature,
* invoking methods defined by Ruby, and getting an instance of a specified interface
* that has been implemented by Ruby.
*
*
* Example 4:
* {@code
* ScriptingContainer container = new ScriptingContainer();
* String script =
* "def message\n" +
* "\"message: #{@message}\"\n" +
* "end\n" +
* "message";
* container.put("@message", "What's up?");
* JavaEmbedUtils.EvalUnit unit = container.parse(script);
* IRubyObject msg = unit.run(); // a RubyString instance
* System.out.println(JavaEmbedUtils.rubyToJava(msg));
* container.put("@message", "Fabulous!");
* msg = unit.run();
* System.out.println(JavaEmbedUtils.rubyToJava(msg));
* container.put("@message", "That's the way you are.");
* msg = unit.run();
* System.out.println(JavaEmbedUtils.rubyToJava(msg));
* }
* Produces:
* message: What's up?
* message: Fabulous!
* message: That's the way you are.
*
*
* See more details at project's
* {@see Wiki}
*
* @author Yoko Harada
*/
public class ScriptingContainer implements EmbedRubyInstanceConfigAdapter {
private final Map basicProperties;
private final LocalContextProvider provider;
private final EmbedRubyRuntimeAdapter runtimeAdapter = new EmbedRubyRuntimeAdapterImpl(this);
private final EmbedRubyObjectAdapter objectAdapter = new EmbedRubyObjectAdapterImpl(this);
private final EmbedRubyInterfaceAdapter interfaceAdapter = new EmbedRubyInterfaceAdapterImpl(this);
/**
* Constructs a ScriptingContainer with a default values.
*/
public ScriptingContainer() {
this(LocalContextScope.SINGLETON, LocalVariableBehavior.TRANSIENT, true);
}
/**
* Constructs a ScriptingContainer with a specified local context type.
*
* @param scope a local context type.
*/
public ScriptingContainer(LocalContextScope scope) {
this(scope, LocalVariableBehavior.TRANSIENT, true);
}
/**
* Constructs a ScriptingContainer with a specified local variable behavior.
*
* @param behavior a local variable behavior
*/
public ScriptingContainer(LocalVariableBehavior behavior) {
this(LocalContextScope.SINGLETON, behavior, true);
}
/**
* Constructs a ScriptingContainer with a specified local context type and
* variable behavior.
*
* @param scope a local context type
* @param behavior a local variable behavior
*/
public ScriptingContainer(LocalContextScope scope, LocalVariableBehavior behavior) {
this(scope, behavior, true);
}
/**
* Constructs a ScriptingContainer with a specified local context scope,
* local variable behavior and laziness.
*
* @param scope is one of a local context scope defined by {@link LocalContextScope}
* @param behavior is one of a local variable behavior defined by {@link LocalVariableBehavior}
* @param lazy is a switch to do lazy retrieval of variables/constants from
* Ruby runtime. Default is true. When this value is true, ScriptingContainer tries to
* get as many variables/constants as possible from Ruby runtime.
*/
public ScriptingContainer(LocalContextScope scope, LocalVariableBehavior behavior, boolean lazy) {
provider = getProviderInstance(scope, behavior, lazy);
try {
initRubyInstanceConfig();
}
catch (RaiseException ex) {
// TODO this seems useless - except that we get a Java stack trace
throw new RuntimeException(ex);
}
basicProperties = getBasicProperties();
}
static LocalContextProvider getProviderInstance(LocalContextScope scope, LocalVariableBehavior behavior, boolean lazy) {
switch(scope) {
case THREADSAFE :
return new ThreadSafeLocalContextProvider(behavior, lazy);
case CONCURRENT :
return new ConcurrentLocalContextProvider(behavior, lazy);
case SINGLETHREAD :
return new SingleThreadLocalContextProvider(behavior, lazy);
case SINGLETON :
default :
return SingletonLocalContextProvider.getProvider(behavior, lazy);
}
}
private void initRubyInstanceConfig() throws RaiseException {
List paths = SystemPropertyCatcher.findLoadPaths();
provider.getRubyInstanceConfig().setLoadPaths(paths);
String home = SystemPropertyCatcher.findJRubyHome(this);
if (home != null) {
provider.getRubyInstanceConfig().setJRubyHome(home);
}
provider.getRubyInstanceConfig().setScriptFileName("