| /* |
| |
| 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 <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 { |
| |
| /** |
| * The number of cached compiled scripts to store. |
| */ |
| 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"; |
| |
| /** |
| * Context vector, to make sure we are not |
| * setting the security context too many times |
| */ |
| protected static List contexts = new LinkedList(); |
| |
| /** |
| * The window object. |
| */ |
| protected Window window; |
| |
| /** |
| * The global object. |
| */ |
| protected ScriptableObject globalObject = null; |
| |
| /** |
| * List of cached compiled scripts. |
| */ |
| protected LinkedList compiledScripts = new LinkedList(); |
| |
| /** |
| * Factory for Java wrapper objects. |
| */ |
| protected WrapFactory wrapFactory = new BatikWrapFactory(this); |
| |
| /** |
| * 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. |
| */ |
| protected RhinoClassLoader rhinoClassLoader; |
| |
| /** |
| * 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. |
| */ |
| protected ContextFactory contextFactory = new Factory(); |
| |
| /** |
| * Default Context for scripts. This is used only for efficiency |
| * reasons. |
| */ |
| protected Context defaultContext; |
| |
| /** |
| * 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 <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. |
| */ |
| public String[] getMimeTypes() { |
| return RhinoInterpreterFactory.RHINO_MIMETYPES; |
| } |
| |
| /** |
| * Returns the window object for this interpreter. |
| */ |
| public Window getWindow() { |
| return window; |
| } |
| |
| /** |
| * Returns the ContextFactory for this interpreter. |
| */ |
| public ContextFactory getContextFactory() { |
| return contextFactory; |
| } |
| |
| /** |
| * 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. |
| */ |
| protected ScriptableObject createGlobalObject(Context ctx) { |
| return new WindowWrapper(ctx); |
| } |
| |
| /** |
| * 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. |
| */ |
| 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 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(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. |
| * @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 <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 <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 <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 <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 <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 <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 <code>EventTargetWrapper</code>. |
| */ |
| Object call(ContextAction action) { |
| return contextFactory.call(action); |
| } |
| |
| /** |
| * 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 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. |
| * @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; |
| } |
| |
| /** |
| * Class to store cached compiled scripts. |
| */ |
| protected static class Entry { |
| |
| /** |
| * The script string. |
| */ |
| public String str; |
| |
| /** |
| * The compiled script. |
| */ |
| public Script script; |
| |
| /** |
| * Creates a new script cache entry object. |
| */ |
| public Entry(String str, Script script) { |
| this.str = str; |
| this.script = script; |
| } |
| } |
| |
| /** |
| * Factory for Context objects. |
| */ |
| protected class Factory extends ContextFactory { |
| |
| /** |
| * 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; |
| } |
| } |
| } |