/*

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You 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.bridge;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
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 java.util.MissingResourceException;

import org.apache.batik.script.Interpreter;
import org.apache.batik.script.InterpreterException;
import org.apache.batik.script.ImportInfo;
import org.apache.batik.script.rhino.BatikSecurityController;
import org.apache.batik.script.rhino.RhinoClassLoader;
import org.apache.batik.script.rhino.RhinoClassShutter;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ClassCache;
import org.mozilla.javascript.ClassShutter;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
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 Interpreter interface to use Rhino ECMAScript interpreter.
Author:Christophe Jolif
Version:$Id: RhinoInterpreter.java 1808888 2017-09-19 14:22:11Z ssteiner $
/** * 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: RhinoInterpreter.java 1808888 2017-09-19 14:22:11Z ssteiner $ */
public class RhinoInterpreter implements Interpreter {
The number of cached compiled scripts to store.
/** * The number of cached compiled scripts to store. */
private static final int MAX_CACHED_SCRIPTS = 32;
Constant used to describe an SVG source
/** * Constant used to describe an SVG source */
public static final String SOURCE_NAME_SVG = "<SVG>";
Name of the "window" object when referenced by scripts
/** * Name of the "window" object when referenced by scripts */
public static final String BIND_NAME_WINDOW = "window";
Context vector, to make sure we are not setting the security context too many times
/** * Context vector, to make sure we are not * setting the security context too many times */
protected static List contexts = new LinkedList();
The window object.
/** * The window object. */
protected Window window;
The global object.
/** * The global object. */
protected ScriptableObject globalObject = null;
List of cached compiled scripts.
/** * List of cached compiled scripts. */
protected LinkedList compiledScripts = new LinkedList();
Factory for Java wrapper objects.
/** * Factory for Java wrapper objects. */
protected WrapFactory wrapFactory = new BatikWrapFactory(this);
Class shutter.
/** * Class shutter. */
protected ClassShutter classShutter = new RhinoClassShutter();
The Rhino 'security domain'. We use the RhinoClassLoader which will grant permissions to connect to the document URL.
/** * 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
/** * The SecurityController implementation for Batik, * which ensures scripts have access to the * server they were downloaded from */
protected SecurityController securityController = new BatikSecurityController();
Factory object for creating Contexts.
/** * Factory object for creating Contexts. */
protected ContextFactory contextFactory = new Factory();
Default Context for scripts. This is used only for efficiency reasons.
/** * Default Context for scripts. This is used only for efficiency * reasons. */
protected Context defaultContext;
Build a Interpreter for ECMAScript using Rhino.
Params:
  • documentURL – the URL for the document which references
See Also:
/** * 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) { init(documentURL, null); }
Build a Interpreter for ECMAScript using Rhino.
Params:
  • documentURL – the URL for the document which references
  • imports – the set of Java classes/packages to import into the scripting enviornment.
See Also:
/** * Build a <code>Interpreter</code> for ECMAScript using Rhino. * * @param documentURL the URL for the document which references * @param imports the set of Java classes/packages to import * into the scripting enviornment. * * @see org.apache.batik.script.Interpreter * @see org.apache.batik.script.InterpreterPool */
public RhinoInterpreter(URL documentURL, ImportInfo imports) { init(documentURL, imports); } protected void init(URL documentURL, final ImportInfo imports) { try { rhinoClassLoader = new RhinoClassLoader (documentURL, getClass().getClassLoader()); } catch (SecurityException se) { rhinoClassLoader = null; } ContextAction initAction = new ContextAction() { public Object run(Context cx) { Scriptable scriptable = cx.initStandardObjects(null, false); defineGlobalWrapperClass(scriptable); globalObject = createGlobalObject(cx); ClassCache cache = ClassCache.get(globalObject); cache.setCachingEnabled(rhinoClassLoader != null); ImportInfo ii = imports; if (ii == null) ii = ImportInfo.getImports(); // import Java lang package & DOM Level 3 & SVG DOM packages StringBuffer sb = new StringBuffer(); Iterator iter; iter = ii.getPackages(); while (iter.hasNext()) { String pkg = (String)iter.next(); sb.append("importPackage(Packages."); sb.append(pkg); sb.append(");"); } iter = ii.getClasses(); while (iter.hasNext()) { String cls = (String)iter.next(); sb.append("importClass(Packages."); sb.append(cls); sb.append(");"); } cx.evaluateString(globalObject, sb.toString(), null, 0, rhinoClassLoader); return null; } }; contextFactory.call(initAction); }
Returns the content types of the scripting languages this interpreter handles.
/** * Returns the content types of the scripting languages this interpreter * handles. */
public String[] getMimeTypes() { return RhinoInterpreterFactory.RHINO_MIMETYPES; }
Returns the window object for this interpreter.
/** * Returns the window object for this interpreter. */
public Window getWindow() { return window; }
Returns the ContextFactory for this interpreter.
/** * Returns the ContextFactory for this interpreter. */
public ContextFactory getContextFactory() { return contextFactory; }
Defines the class for the global object.
/** * Defines the class for the global object. */
protected void defineGlobalWrapperClass(Scriptable global) { try { ScriptableObject.defineClass(global, WindowWrapper.class); } catch (Exception ex) { // cannot happen } }
Creates the global object.
/** * Creates the global object. */
protected ScriptableObject createGlobalObject(Context ctx) { return new WindowWrapper(ctx); }
Returns the AccessControlContext associated with this Interpreter.
See Also:
  • RhinoClassLoader
/** * Returns the AccessControlContext associated with this Interpreter. * @see org.apache.batik.script.rhino.RhinoClassLoader */
public AccessControlContext getAccessControlContext() { if (rhinoClassLoader == null) return null; return rhinoClassLoader.getAccessControlContext(); }
This method returns the ECMAScript global object used by this interpreter.
/** * 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.
Params:
  • scriptreader – a java.io.Reader on the piece of script
Returns:if no exception is thrown during the call, should return the value of the last expression evaluated in the script.
/** * 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 IOException { return evaluate(scriptreader, SOURCE_NAME_SVG); }
This method evaluates a piece of ECMAScript.
Params:
  • scriptReader – a java.io.Reader on the piece of script
  • description – description which can be later used (e.g., for error messages).
Returns:if no exception is thrown during the call, should return the value of the last expression evaluated in the script.
/** * 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(final Reader scriptReader, final String description) throws IOException { ContextAction evaluateAction = new ContextAction() { public Object run(Context cx) { try { return cx.evaluateReader(globalObject, scriptReader, description, 1, rhinoClassLoader); } catch (IOException ioe) { throw new WrappedException(ioe); } } }; try { return contextFactory.call(evaluateAction); } catch (JavaScriptException e) { // exception from JavaScript (possibly wrapping a Java Ex) Object value = e.getValue(); Exception ex = value instanceof Exception ? (Exception) value : e; throw new InterpreterException(ex, ex.getMessage(), -1, -1); } catch (WrappedException we) { Throwable w = we.getWrappedException(); if (w instanceof Exception) { throw new InterpreterException ((Exception) w, w.getMessage(), -1, -1); } else { throw new InterpreterException(w.getMessage(), -1, -1); } } catch (InterruptedBridgeException ibe) { throw ibe; } catch (RuntimeException re) { throw new InterpreterException(re, re.getMessage(), -1, -1); } }
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.
Params:
  • scriptStr – the piece of script
Returns:if no exception is thrown during the call, should return the value of the last expression evaluated in the script.
/** * 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) { ContextAction evalAction = new ContextAction() { public Object run(final Context cx) { Script script = null; Entry entry = null; Iterator it = compiledScripts.iterator(); // between nlog(n) and log(n) because it is // an AbstractSequentialList while (it.hasNext()) { if ((entry = (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 = entry.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. PrivilegedAction compile = new PrivilegedAction() { public Object run() { try { return cx.compileReader (new StringReader(scriptStr), SOURCE_NAME_SVG, 1, rhinoClassLoader); } catch (IOException ioEx ) { // Should never happen: using a string throw new RuntimeException( ioEx.getMessage() ); } } }; script = (Script)AccessController.doPrivileged(compile); 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(); } // storing is done here: compiledScripts.addLast(new Entry(scriptStr, script)); } else { // this script has been compiled before, // just update its index so it won't get deleted soon. compiledScripts.addLast(entry); } return script.exec(cx, globalObject); } }; try { return contextFactory.call(evalAction); } catch (InterpreterException ie) { throw ie; } catch (JavaScriptException e) { // exception from JavaScript (possibly wrapping a Java Ex) Object value = e.getValue(); Exception ex = value instanceof Exception ? (Exception) value : e; throw new InterpreterException(ex, ex.getMessage(), -1, -1); } catch (WrappedException we) { Throwable w = we.getWrappedException(); if (w instanceof Exception) { throw new InterpreterException ((Exception) w, w.getMessage(), -1, -1); } else { throw new InterpreterException(w.getMessage(), -1, -1); } } catch (RuntimeException re) { throw new InterpreterException(re, re.getMessage(), -1, -1); } }
For RhinoInterpreter this method flushes the Rhino caches to avoid memory leaks.
/** * For <code>RhinoInterpreter</code> this method flushes the * Rhino caches to avoid memory leaks. */
public void dispose() { if (rhinoClassLoader != null) { ClassCache cache = ClassCache.get(globalObject); cache.setCachingEnabled(false); } }
This method registers a particular Java Object in the environment of the interpreter.
Params:
  • name – the name of the script object to create
  • object – the Java object
/** * 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(final String name, final Object object) { contextFactory.call(new ContextAction() { public Object run(Context cx) { Object o = object; if (name.equals(BIND_NAME_WINDOW) && object instanceof Window) { ((WindowWrapper) globalObject).window = (Window) object; window = (Window) object; o = globalObject; } Scriptable jsObject; jsObject = Context.toObject(o, globalObject); globalObject.put(name, globalObject, jsObject); return null; } }); }
To be used by EventTargetWrapper.
/** * To be used by <code>EventTargetWrapper</code>. */
void callHandler(final Function handler, final Object arg) { contextFactory.call(new ContextAction() { public Object run(Context cx) { Object a = Context.toObject(arg, globalObject); Object[] args = { a }; handler.call(cx, globalObject, globalObject, args); return null; } }); }
To be used by WindowWrapper.
/** * To be used by <code>WindowWrapper</code>. */
void callMethod(final ScriptableObject obj, final String methodName, final ArgumentsBuilder ab) { contextFactory.call(new ContextAction() { public Object run(Context cx) { ScriptableObject.callMethod (obj, methodName, ab.buildArguments()); return null; } }); }
To be used by WindowWrapper.
/** * To be used by <code>WindowWrapper</code>. */
void callHandler(final Function handler, final Object[] args) { contextFactory.call(new ContextAction() { public Object run(Context cx) { handler.call(cx, globalObject, globalObject, args); return null; } }); }
To be used by WindowWrapper.
/** * To be used by <code>WindowWrapper</code>. */
void callHandler(final Function handler, final ArgumentsBuilder ab) { contextFactory.call(new ContextAction() { public Object run(Context cx) { Object[] args = ab.buildArguments(); handler.call(cx, handler.getParentScope(), globalObject, args); return null; } }); }
To be used by EventTargetWrapper.
/** * To be used by <code>EventTargetWrapper</code>. */
Object call(ContextAction action) { return contextFactory.call(action); }
To build an argument list.
/** * To build an argument list. */
public interface ArgumentsBuilder { Object[] buildArguments(); }
Build the wrapper for objects implement EventTarget.
/** * 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.
Params:
  • out – the new out Writer.
/** * 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.
/** * Returns the current locale or null if the locale currently used is * the default one. */
public Locale getLocale() { // <!> TODO : in Rhino the locale 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.
Params:
  • locale – The locale to set.
/** * 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.
Params:
  • key – The key used to retreive the message from the resource bundle.
  • args – The objects that compose the message.
Throws:
/** * 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; }
Class to store cached compiled scripts.
/** * Class to store cached compiled scripts. */
protected static class Entry {
The script string.
/** * The script string. */
public String str;
The compiled script.
/** * The compiled script. */
public Script script;
Creates a new script cache entry object.
/** * Creates a new script cache entry object. */
public Entry(String str, Script script) { this.str = str; this.script = script; } }
Factory for Context objects.
/** * Factory for Context objects. */
protected class Factory extends ContextFactory {
Creates a Context object for use with the interpreter.
/** * Creates a Context object for use with the interpreter. */
protected Context makeContext() { Context cx = super.makeContext(); cx.setWrapFactory(wrapFactory); cx.setSecurityController(securityController); cx.setClassShutter(classShutter); if (rhinoClassLoader == null) { cx.setOptimizationLevel(-1); } return cx; } } }