blob: eabe876f1b4afec847a0e2b8cf87c6f81b6c99bb [file] [log] [blame]
/*
Copyright 2001-2004 The Apache Software Foundation
Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.batik.script.rhino;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import org.apache.batik.bridge.InterruptedBridgeException;
import org.apache.batik.script.Interpreter;
import org.apache.batik.script.InterpreterException;
import org.apache.batik.script.Window;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeJavaPackage;
import org.mozilla.javascript.PropertyException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.SecurityController;
import org.mozilla.javascript.WrapFactory;
import org.mozilla.javascript.WrappedException;
import org.w3c.dom.events.EventTarget;
/**
* A simple implementation of <code>Interpreter</code> interface to use
* Rhino ECMAScript interpreter.
* @author <a href="mailto:cjolif@ilog.fr">Christophe Jolif</a>
* @version $Id$
*/
public class RhinoInterpreter implements Interpreter {
private static String[] TO_BE_IMPORTED = {
"java.lang",
"org.w3c.dom",
"org.w3c.dom.css",
"org.w3c.dom.events",
"org.w3c.dom.smil",
"org.w3c.dom.stylesheets",
"org.w3c.dom.svg",
"org.w3c.dom.views"
};
/**
* The window object
*/
protected Window window;
public Window getWindow() {
return window;
}
private static class Entry {
String str;
Script script;
Entry(String str, Script script) {
this.str = str;
this.script = script;
}
}
/**
* store last 32 precompiled objects.
*/
private static final int MAX_CACHED_SCRIPTS = 32;
/**
* Constant used to describe an SVG source
*/
public static final String SOURCE_NAME_SVG = "<SVG>";
/**
* Name of the "window" object when referenced by scripts
*/
public static final String BIND_NAME_WINDOW = "window";
private ScriptableObject globalObject = null;
private LinkedList compiledScripts = new LinkedList();
private WrapFactory wrapFactory = new BatikWrapFactory(this);
/**
* The Rhino 'security domain'. We use the RhinoClassLoader
* which will grant permissions to connect to the document
* URL.
*/
protected RhinoClassLoader rhinoClassLoader;
/**
* The SecurityController implementation for Batik,
* which ensures scripts have access to the
* server they were downloaded from
*/
private SecurityController securityController
= new BatikSecurityController();
/**
* Default Context for scripts. This is used only for efficiency
* reason.
*/
protected Context defaultContext;
/**
* Context vector, to make sure we are not
* setting the security context too many times
*/
protected List contexts = new LinkedList();
/**
* Build a <code>Interpreter</code> for ECMAScript using Rhino.
*
* @param documentURL the URL for the document which references
*
* @see org.apache.batik.script.Interpreter
* @see org.apache.batik.script.InterpreterPool
*/
public RhinoInterpreter(URL documentURL) {
try {
rhinoClassLoader = new RhinoClassLoader
(documentURL, getClass().getClassLoader());
} catch (SecurityException se) {
rhinoClassLoader = null;
}
// entering a context
Context ctx = enterContext();
try {
try {
Scriptable scriptable = ctx.initStandardObjects(null, false);
ScriptableObject.defineClass(scriptable, WindowWrapper.class);
} catch (Exception e) {
// cannot happen
}
// we now have the window object as the global object from the
// launch of the interpreter.
// 1. it works around a Rhino bug introduced in 15R4 (but fixed
// by a later patch).
// 2. it sounds cleaner.
WindowWrapper wWrapper = new WindowWrapper(ctx);
globalObject = wWrapper;
// import Java lang package & DOM Level 2 & SVG DOM packages
NativeJavaPackage[] p= new NativeJavaPackage[TO_BE_IMPORTED.length];
for (int i = 0; i < TO_BE_IMPORTED.length; i++) {
p[i] = new NativeJavaPackage(TO_BE_IMPORTED[i], rhinoClassLoader);
} try {
ScriptableObject.callMethod(globalObject, "importPackage", p);
} catch (JavaScriptException e) {
// cannot happen as we know the method is there and
// the parameters are ok
}
} finally {
Context.exit();
}
}
/**
* Returns the AccessControlContext associated with this Interpreter.
* @see org.apache.batik.script.rhino.RhinoClassLoader
*/
public AccessControlContext getAccessControlContext(){
return rhinoClassLoader.getAccessControlContext();
}
/**
* Implementation helper. Makes sure the proper security is set
* on the context.
*/
public Context enterContext(){
Context ctx = Context.enter();
if (ctx == defaultContext)
return ctx;
if (!contexts.contains(ctx)) {
ctx.setWrapFactory(wrapFactory);
ctx.setSecurityController(securityController);
ctx.setClassShutter(new RhinoClassShutter());
// No class loader so don't try and optmize.
if (rhinoClassLoader == null) {
ctx.setOptimizationLevel(-1);
ctx.setCachingEnabled(false);
}
contexts.add(ctx);
}
defaultContext = ctx;
return ctx;
}
/**
* This method returns the ECMAScript global object used by this
* interpreter.
*/
protected ScriptableObject getGlobalObject() {
return globalObject;
}
// org.apache.batik.script.Intepreter implementation
/**
* This method evaluates a piece of ECMAScript.
* @param scriptreader a <code>java.io.Reader</code> on the piece of script
* @return if no exception is thrown during the call, should return the
* value of the last expression evaluated in the script.
*/
public Object evaluate(Reader scriptreader)
throws InterpreterException, IOException {
return evaluate(scriptreader, SOURCE_NAME_SVG);
}
/**
* This method evaluates a piece of ECMAScript.
* @param scriptreader a <code>java.io.Reader</code> on the piece of script
* @param description description which can be later used (e.g., for error
* messages).
* @return if no exception is thrown during the call, should return the
* value of the last expression evaluated in the script.
*/
public Object evaluate(Reader scriptreader, String description)
throws InterpreterException, IOException {
Object rv = null;
final Context ctx = enterContext();
try {
rv = ctx.evaluateReader(globalObject,
scriptreader,
description,
1, rhinoClassLoader);
} catch (JavaScriptException e) {
// exception from JavaScript (possibly wrapping a Java Ex)
if (e.getValue() instanceof Exception) {
Exception ex = (Exception)e.getValue();
throw new InterpreterException(ex, ex.getMessage(), -1, -1);
} else
throw new InterpreterException(e, e.getMessage(), -1, -1);
} catch (WrappedException we) {
// main Rhino RuntimeException
Throwable w = we.getWrappedException();
if (w instanceof Exception)
throw
new InterpreterException((Exception)we.getWrappedException(),
we.getWrappedException().getMessage(),
-1, -1);
else
throw new InterpreterException(we.getWrappedException().getMessage(), -1, -1);
} catch (InterruptedBridgeException ibe) {
// This sometimes happens when script builds stuff.
throw ibe;
} catch (RuntimeException re) {
// other RuntimeExceptions
throw new InterpreterException(re, re.getMessage(), -1, -1);
} finally {
Context.exit();
}
return rv;
}
/**
* This method evaluates a piece of ECMA script.
* The first time a String is passed, it is compiled and evaluated.
* At next call, the piece of script will only be evaluated to
* prevent from recompiling it.
* @param scriptstr the piece of script
* @return if no exception is thrown during the call, should return the
* value of the last expression evaluated in the script.
*/
public Object evaluate(final String scriptstr)
throws InterpreterException {
Object rv = null;
final Context ctx = enterContext();
try {
Script script = null;
Entry et = null;
Iterator it = compiledScripts.iterator();
// between nlog(n) and log(n) because it is
// an AbstractSequentialList
while (it.hasNext()) {
if ((et = (Entry)(it.next())).str.equals(scriptstr)) {
// if it is not at the end, remove it because
// it will change from place (it is faster
// to remove it now)
script = et.script;
it.remove();
break;
}
}
if (script == null) {
// this script has not been compiled yet or has been forgotten
// since the compilation:
// compile it and store it for future use.
script = (Script)AccessController.doPrivileged
(new PrivilegedAction() {
public Object run() {
try {
return ctx.compileReader
(globalObject,
new StringReader(scriptstr),
SOURCE_NAME_SVG,
1, rhinoClassLoader);
} catch (IOException io) {
// Should never happen: using a string
throw new Error();
}
}
});
if (compiledScripts.size()+1 > MAX_CACHED_SCRIPTS) {
// too many cached items - we should delete the
// oldest entry. all of this is very fast on
// linkedlist
compiledScripts.removeFirst();
}
// stroring is done here:
compiledScripts.addLast(new Entry(scriptstr, script));
} else {
// this script has been compiled before,
// just update it's index so it won't get deleted soon.
compiledScripts.addLast(et);
}
try {
rv = script.exec(ctx, globalObject);
} catch (JavaScriptException e) {
// exception from JavaScript (possibly wrapping a Java Ex)
if (e.getValue() instanceof Exception) {
Exception ex = (Exception)e.getValue();
throw new InterpreterException(ex, ex.getMessage(), -1,-1);
} else
throw new InterpreterException(e, e.getMessage(), -1, -1);
} catch (WrappedException we) {
// main Rhino RuntimeException
throw
new InterpreterException
((Exception)we.getWrappedException(),
we.getWrappedException().getMessage(), -1, -1);
} catch (RuntimeException re) {
// other RuntimeExceptions
throw new InterpreterException(re, re.getMessage(), -1, -1);
}
} finally {
Context.exit();
}
return rv;
}
/**
* For <code>RhinoInterpreter</code> this method flushes the
* Rhino caches to avoid memory leaks.
*/
public void dispose() {
if (rhinoClassLoader != null) {
Context.setCachingEnabled(false);
Context.setCachingEnabled(true);
}
}
/**
* This method registers a particular Java <code>Object</code> in
* the environment of the interpreter.
* @param name the name of the script object to create
* @param object the Java object
*/
public void bindObject(String name, Object object) {
enterContext();
try {
if (name.equals(BIND_NAME_WINDOW) && object instanceof Window) {
((WindowWrapper)globalObject).window = (Window)object;
window = (Window)object;
object = globalObject;
}
Scriptable jsObject;
jsObject = Context.toObject(object, globalObject);
globalObject.put(name, globalObject, jsObject);
} finally {
Context.exit();
}
}
/**
* To be used by <code>EventTargetWrapper</code>.
*/
void callHandler(Function handler, Object arg)
throws JavaScriptException {
Context ctx = enterContext();
try {
arg = Context.toObject(arg, globalObject);
Object[] args = {arg};
handler.call(ctx, globalObject, globalObject, args);
} finally {
Context.exit();
}
}
/**
* To be used by <code>WindowWrapper</code>.
*/
void callMethod(ScriptableObject obj,
String methodName,
ArgumentsBuilder ab)
throws JavaScriptException {
enterContext();
try {
ScriptableObject.callMethod(obj, methodName, ab.buildArguments());
} finally {
Context.exit();
}
}
/**
* To be used by <code>WindowWrapper</code>.
*/
void callHandler(Function handler,
Object[] args)
throws JavaScriptException {
Context ctx = enterContext();
try {
handler.call(ctx, globalObject, globalObject, args);
} finally {
Context.exit();
}
}
/**
* To be used by <code>WindowWrapper</code>.
*/
void callHandler(Function handler, ArgumentsBuilder ab)
throws JavaScriptException {
Context ctx = enterContext();
try {
Object [] args = ab.buildArguments();
handler.call(ctx, handler.getParentScope(), globalObject, args );
} finally {
Context.exit();
}
}
/**
* To build an argument list.
*/
public interface ArgumentsBuilder {
Object[] buildArguments();
}
/**
* Build the wrapper for objects implement <code>EventTarget</code>.
*/
Scriptable buildEventTargetWrapper(EventTarget obj) {
return new EventTargetWrapper(globalObject, obj, this);
}
/**
* By default Rhino has no output method in its language. That's why
* this method does nothing.
* @param out the new out <code>Writer</code>.
*/
public void setOut(Writer out) {
// no implementation of a default output function in Rhino
}
// org.apache.batik.i18n.Localizable implementation
/**
* Returns the current locale or null if the locale currently used is
* the default one.
*/
public Locale getLocale() {
// <!> TODO : in Rhino the local is for a thread not a scope..
return null;
}
/**
* Provides a way to the user to specify a locale which override the
* default one. If null is passed to this method, the used locale
* becomes the global one.
* @param locale The locale to set.
*/
public void setLocale(Locale locale) {
// <!> TODO : in Rhino the local is for a thread not a scope..
}
/**
* Creates and returns a localized message, given the key of the message, 0, data.length
* in the resource bundle and the message parameters.
* The messages in the resource bundle must have the syntax described in
* the java.text.MessageFormat class documentation.
* @param key The key used to retreive the message from the resource
* bundle.
* @param args The objects that compose the message.
* @exception MissingResourceException if the key is not in the bundle.
*/
public String formatMessage(String key, Object[] args) {
return null;
}
}