KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > batik > script > rhino > RhinoInterpreter


1 /*
2
3    Copyright 2001-2004 The Apache Software Foundation
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6    you may not use this file except in compliance with the License.
7    You may obtain a copy of the License at
8
9        http://www.apache.org/licenses/LICENSE-2.0
10
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16
17  */

18 package org.apache.batik.script.rhino;
19
20 import java.io.IOException JavaDoc;
21 import java.io.Reader JavaDoc;
22 import java.io.StringReader JavaDoc;
23 import java.io.Writer JavaDoc;
24 import java.lang.reflect.Method JavaDoc;
25 import java.net.URL JavaDoc;
26 import java.security.AccessControlContext JavaDoc;
27 import java.security.AccessController JavaDoc;
28 import java.security.PrivilegedAction JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.LinkedList JavaDoc;
31 import java.util.Locale JavaDoc;
32 import java.util.Map JavaDoc;
33 import java.util.HashMap JavaDoc;
34
35 import org.apache.batik.bridge.InterruptedBridgeException;
36 import org.apache.batik.script.Interpreter;
37 import org.apache.batik.script.InterpreterException;
38 import org.apache.batik.script.Window;
39 import org.mozilla.javascript.Context;
40 import org.mozilla.javascript.Function;
41 import org.mozilla.javascript.JavaScriptException;
42 import org.mozilla.javascript.NativeJavaPackage;
43 import org.mozilla.javascript.PropertyException;
44 import org.mozilla.javascript.Script;
45 import org.mozilla.javascript.Scriptable;
46 import org.mozilla.javascript.ScriptableObject;
47 import org.mozilla.javascript.SecurityController;
48 import org.mozilla.javascript.WrapFactory;
49 import org.mozilla.javascript.WrappedException;
50 import org.w3c.dom.events.EventTarget JavaDoc;
51
52 /**
53  * A simple implementation of <code>Interpreter</code> interface to use
54  * Rhino ECMAScript interpreter.
55  * @author <a HREF="mailto:cjolif@ilog.fr">Christophe Jolif</a>
56  * @version $Id: RhinoInterpreter.java,v 1.41 2005/03/29 10:48:02 deweese Exp $
57  */

58 public class RhinoInterpreter implements Interpreter {
59     private static String JavaDoc[] TO_BE_IMPORTED = {
60         "java.lang",
61         "org.w3c.dom",
62         "org.w3c.dom.css",
63         "org.w3c.dom.events",
64         "org.w3c.dom.smil",
65         "org.w3c.dom.stylesheets",
66         "org.w3c.dom.svg",
67         "org.w3c.dom.views"
68     };
69
70     /**
71      * The window object
72      */

73     protected Window window;
74
75     public Window getWindow() {
76         return window;
77     }
78
79
80     private static class Entry {
81         String JavaDoc str;
82         Script script;
83         Entry(String JavaDoc str, Script script) {
84             this.str = str;
85             this.script = script;
86         }
87     }
88
89     /**
90      * store last 32 precompiled objects.
91      */

92     private static final int MAX_CACHED_SCRIPTS = 32;
93
94     /**
95      * Constant used to describe an SVG source
96      */

97     public static final String JavaDoc SOURCE_NAME_SVG = "<SVG>";
98
99     /**
100      * Name of the "window" object when referenced by scripts
101      */

102     public static final String JavaDoc BIND_NAME_WINDOW = "window";
103
104     private ScriptableObject globalObject = null;
105     private LinkedList JavaDoc compiledScripts = new LinkedList JavaDoc();
106     private WrapFactory wrapFactory =
107         new BatikWrapFactory(this);
108
109     /**
110      * The Rhino 'security domain'. We use the RhinoClassLoader
111      * which will grant permissions to connect to the document
112      * URL.
113      */

114     protected RhinoClassLoader rhinoClassLoader;
115
116     /**
117      * The SecurityController implementation for Batik,
118      * which ensures scripts have access to the
119      * server they were downloaded from
120      */

121     private SecurityController securityController
122         = new BatikSecurityController();
123
124     /**
125      * Build a <code>Interpreter</code> for ECMAScript using Rhino.
126      *
127      * @param documentURL the URL for the document which references
128      *
129      * @see org.apache.batik.script.Interpreter
130      * @see org.apache.batik.script.InterpreterPool
131      */

132     public RhinoInterpreter(URL JavaDoc documentURL) {
133         try {
134             rhinoClassLoader = new RhinoClassLoader
135                 (documentURL, getClass().getClassLoader());
136         } catch (SecurityException JavaDoc se) {
137             rhinoClassLoader = null;
138         }
139         // entering a context
140
Context JavaDoc ctx = enterContext();
141         try {
142             try {
143                 Scriptable scriptable = ctx.initStandardObjects(null, false);
144                 ScriptableObject.defineClass(scriptable, WindowWrapper.class);
145             } catch (Exception JavaDoc e) {
146                 // cannot happen
147
}
148             // we now have the window object as the global object from the
149
// launch of the interpreter.
150
// 1. it works around a Rhino bug introduced in 15R4 (but fixed
151
// by a later patch).
152
// 2. it sounds cleaner.
153
WindowWrapper wWrapper = new WindowWrapper(ctx);
154             globalObject = wWrapper;
155             // import Java lang package & DOM Level 2 & SVG DOM packages
156
NativeJavaPackage[] p= new NativeJavaPackage[TO_BE_IMPORTED.length];
157             for (int i = 0; i < TO_BE_IMPORTED.length; i++) {
158                 p[i] = new NativeJavaPackage(TO_BE_IMPORTED[i], rhinoClassLoader);
159             } try {
160                 ScriptableObject.callMethod(globalObject, "importPackage", p);
161             } catch (JavaScriptException e) {
162               // cannot happen as we know the method is there and
163
// the parameters are ok
164
}
165         } finally {
166             Context.exit();
167         }
168     }
169
170     /**
171      * Returns the AccessControlContext associated with this Interpreter.
172      * @see org.apache.batik.script.rhino.RhinoClassLoader
173      */

174     public AccessControlContext JavaDoc getAccessControlContext(){
175         return rhinoClassLoader.getAccessControlContext();
176     }
177
178     /**
179      * Implementation helper. Makes sure the proper security is set
180      * on the context.
181      */

182     public Context JavaDoc enterContext(){
183         Context JavaDoc ctx = Context.getCurrentContext();
184         if (ctx == null) {
185             ctx = new ExtendedContext();
186             ctx.setWrapFactory(wrapFactory);
187             ctx.setSecurityController(securityController);
188             ctx.setClassShutter(new RhinoClassShutter());
189
190             // No class loader so don't try and optmize.
191
if (rhinoClassLoader == null) {
192                 ctx.setOptimizationLevel(-1);
193                 ctx.setCachingEnabled(false);
194             }
195         }
196         ctx = Context.enter(ctx);
197
198         return ctx;
199     }
200
201     /**
202      * This method returns the ECMAScript global object used by this
203      * interpreter.
204      */

205     protected ScriptableObject getGlobalObject() {
206         return globalObject;
207     }
208
209     // org.apache.batik.script.Intepreter implementation
210

211     /**
212      * This method evaluates a piece of ECMAScript.
213      * @param scriptreader a <code>java.io.Reader</code> on the piece of script
214      * @return if no exception is thrown during the call, should return the
215      * value of the last expression evaluated in the script.
216      */

217     public Object JavaDoc evaluate(Reader JavaDoc scriptreader)
218         throws InterpreterException, IOException JavaDoc {
219         return evaluate(scriptreader, SOURCE_NAME_SVG);
220     }
221
222     /**
223      * This method evaluates a piece of ECMAScript.
224      * @param scriptreader a <code>java.io.Reader</code> on the piece of script
225      * @param description description which can be later used (e.g., for error
226      * messages).
227      * @return if no exception is thrown during the call, should return the
228      * value of the last expression evaluated in the script.
229      */

230     public Object JavaDoc evaluate(Reader JavaDoc scriptreader, String JavaDoc description)
231         throws InterpreterException, IOException JavaDoc {
232
233         Object JavaDoc rv = null;
234         final Context JavaDoc ctx = enterContext();
235         try {
236             rv = ctx.evaluateReader(globalObject,
237                                     scriptreader,
238                                     description,
239                                     1, rhinoClassLoader);
240         } catch (JavaScriptException e) {
241             // exception from JavaScript (possibly wrapping a Java Ex)
242
if (e.getValue() instanceof Exception JavaDoc) {
243                 Exception JavaDoc ex = (Exception JavaDoc)e.getValue();
244                 throw new InterpreterException(ex, ex.getMessage(), -1, -1);
245             } else
246                 throw new InterpreterException(e, e.getMessage(), -1, -1);
247         } catch (WrappedException we) {
248             // main Rhino RuntimeException
249
Throwable JavaDoc w = we.getWrappedException();
250             if (w instanceof Exception JavaDoc)
251                 throw
252                     new InterpreterException((Exception JavaDoc)we.getWrappedException(),
253                                              we.getWrappedException().getMessage(),
254                                              -1, -1);
255             else
256                 throw new InterpreterException(we.getWrappedException().getMessage(), -1, -1);
257         } catch (InterruptedBridgeException ibe) {
258             // This sometimes happens when script builds stuff.
259
throw ibe;
260         } catch (RuntimeException JavaDoc re) {
261             // other RuntimeExceptions
262
throw new InterpreterException(re, re.getMessage(), -1, -1);
263         } finally {
264             Context.exit();
265         }
266         return rv;
267     }
268
269     /**
270      * This method evaluates a piece of ECMA script.
271      * The first time a String is passed, it is compiled and evaluated.
272      * At next call, the piece of script will only be evaluated to
273      * prevent from recompiling it.
274      * @param scriptstr the piece of script
275      * @return if no exception is thrown during the call, should return the
276      * value of the last expression evaluated in the script.
277      */

278     public Object JavaDoc evaluate(final String JavaDoc scriptstr)
279         throws InterpreterException {
280         Object JavaDoc rv = null;
281         final Context JavaDoc ctx = enterContext();
282         try {
283             Script script = null;
284             Entry et = null;
285             Iterator JavaDoc it = compiledScripts.iterator();
286             // between nlog(n) and log(n) because it is
287
// an AbstractSequentialList
288
while (it.hasNext()) {
289                 if ((et = (Entry)(it.next())).str.equals(scriptstr)) {
290                     // if it is not at the end, remove it because
291
// it will change from place (it is faster
292
// to remove it now)
293
script = et.script;
294                     it.remove();
295                     break;
296                 }
297             }
298
299             if (script == null) {
300                 // this script has not been compiled yet or has been forgotten
301
// since the compilation:
302
// compile it and store it for future use.
303

304                 script = (Script)AccessController.doPrivileged
305                     (new PrivilegedAction JavaDoc() {
306                             public Object JavaDoc run() {
307                                 try {
308                                     return ctx.compileReader
309                                         (globalObject,
310                                          new StringReader JavaDoc(scriptstr),
311                                          SOURCE_NAME_SVG,
312                                          1, rhinoClassLoader);
313                                 } catch (IOException JavaDoc io) {
314                                     // Should never happen: using a string
315
throw new Error JavaDoc();
316                                 }
317                             }
318                         });
319
320                 if (compiledScripts.size()+1 > MAX_CACHED_SCRIPTS) {
321                     // too many cached items - we should delete the
322
// oldest entry. all of this is very fast on
323
// linkedlist
324
compiledScripts.removeFirst();
325                 }
326                 // stroring is done here:
327
compiledScripts.addLast(new Entry(scriptstr, script));
328             } else {
329                 // this script has been compiled before,
330
// just update it's index so it won't get deleted soon.
331
compiledScripts.addLast(et);
332             }
333
334             try {
335                 rv = script.exec(ctx, globalObject);
336             } catch (JavaScriptException e) {
337                 // exception from JavaScript (possibly wrapping a Java Ex)
338
if (e.getValue() instanceof Exception JavaDoc) {
339                     Exception JavaDoc ex = (Exception JavaDoc)e.getValue();
340                     throw new InterpreterException(ex, ex.getMessage(), -1,-1);
341                 } else
342                     throw new InterpreterException(e, e.getMessage(), -1, -1);
343             } catch (WrappedException we) {
344                 // main Rhino RuntimeException
345
throw
346                     new InterpreterException
347                     ((Exception JavaDoc)we.getWrappedException(),
348                      we.getWrappedException().getMessage(), -1, -1);
349             } catch (RuntimeException JavaDoc re) {
350                 // other RuntimeExceptions
351
throw new InterpreterException(re, re.getMessage(), -1, -1);
352             }
353
354         } finally {
355             Context.exit();
356         }
357         return rv;
358     }
359
360     /**
361      * For <code>RhinoInterpreter</code> this method flushes the
362      * Rhino caches to avoid memory leaks.
363      */

364     public void dispose() {
365         if (rhinoClassLoader != null) {
366             Context.setCachingEnabled(false);
367             Context.setCachingEnabled(true);
368         }
369     }
370
371     /**
372      * This method registers a particular Java <code>Object</code> in
373      * the environment of the interpreter.
374      * @param name the name of the script object to create
375      * @param object the Java object
376      */

377     public void bindObject(String JavaDoc name, Object JavaDoc object) {
378         enterContext();
379         try {
380             if (name.equals(BIND_NAME_WINDOW) && object instanceof Window) {
381                 window = (Window)object;
382                 object = globalObject;
383             }
384             try {
385                 Scriptable jsObject;
386                 jsObject = Context.toObject(object, globalObject);
387                 objects.put(name, jsObject);
388                 if (ScriptableObject.getProperty(globalObject, name) ==
389                     ScriptableObject.NOT_FOUND)
390                     globalObject.defineProperty
391                         (name, new RhinoGetDelegate(name),
392                          rhinoGetter, null, ScriptableObject.READONLY);
393             } catch (PropertyException pe) {
394                 pe.printStackTrace();
395             }
396         } finally {
397             Context.exit();
398         }
399     }
400     /**
401      * HashTable to store properties bounds on the global object.
402      * So they don't end up in the JavaMethods static table.
403      */

404     Map JavaDoc objects = new HashMap JavaDoc(4);
405
406     /**
407      * Class to act as 'get' delegate for Rhino. This uses the
408      * currentContext to get the current Interpreter object which
409      * allows it to lookup the object requested. This gets around the
410      * fact that the global object gets referenced from a static
411      * context but the Context does not.
412      */

413     public static class RhinoGetDelegate {
414         String JavaDoc name;
415         RhinoGetDelegate(String JavaDoc name) {
416             this.name = name;
417         }
418         public Object JavaDoc get(ScriptableObject so) {
419             Context JavaDoc ctx = Context.getCurrentContext();
420             if (ctx == null ) return null;
421             return ((ExtendedContext)ctx).getInterpreter().objects.get(name);
422         }
423     }
424     // The method to use for getting the value from the
425
// RhinoGetDelegate.
426
static Method JavaDoc rhinoGetter;
427     static {
428         try {
429             Class JavaDoc [] getterArgs = { ScriptableObject.class };
430             rhinoGetter = RhinoGetDelegate.class.getDeclaredMethod
431                 ("get", getterArgs);
432         } catch (NoSuchMethodException JavaDoc nsm) { }
433     }
434
435
436     /**
437      * To be used by <code>EventTargetWrapper</code>.
438      */

439     void callHandler(Function handler,
440                      Object JavaDoc arg)
441         throws JavaScriptException {
442         Context JavaDoc ctx = enterContext();
443         try {
444             arg = Context.toObject(arg, globalObject);
445             Object JavaDoc[] args = {arg};
446             handler.call(ctx, globalObject, globalObject, args);
447         } finally {
448             Context.exit();
449         }
450     }
451
452     /**
453      * To be used by <code>WindowWrapper</code>.
454      */

455     void callMethod(ScriptableObject obj,
456                     String JavaDoc methodName,
457                     ArgumentsBuilder ab)
458         throws JavaScriptException {
459         enterContext();
460         try {
461             ScriptableObject.callMethod(obj, methodName, ab.buildArguments());
462         } finally {
463             Context.exit();
464         }
465     }
466
467     /**
468      * To be used by <code>WindowWrapper</code>.
469      */

470     void callHandler(Function handler,
471                      Object JavaDoc[] args)
472         throws JavaScriptException {
473         Context JavaDoc ctx = enterContext();
474         try {
475             handler.call(ctx, globalObject, globalObject, args);
476         } finally {
477             Context.exit();
478         }
479     }
480
481     /**
482      * To be used by <code>WindowWrapper</code>.
483      */

484     void callHandler(Function handler, ArgumentsBuilder ab)
485         throws JavaScriptException {
486         Context JavaDoc ctx = enterContext();
487         try {
488             Object JavaDoc [] args = ab.buildArguments();
489            handler.call(ctx, handler.getParentScope(), globalObject, args );
490         } finally {
491             Context.exit();
492         }
493     }
494
495     /**
496      * To build an argument list.
497      */

498     public interface ArgumentsBuilder {
499         Object JavaDoc[] buildArguments();
500     }
501
502     /**
503      * Build the wrapper for objects implement <code>EventTarget</code>.
504      */

505     Scriptable buildEventTargetWrapper(EventTarget JavaDoc obj) {
506         return new EventTargetWrapper(globalObject, obj);
507     }
508
509     /**
510      * By default Rhino has no output method in its language. That's why
511      * this method does nothing.
512      * @param out the new out <code>Writer</code>.
513      */

514     public void setOut(Writer JavaDoc out) {
515         // no implementation of a default output function in Rhino
516
}
517
518     // org.apache.batik.i18n.Localizable implementation
519

520     /**
521      * Returns the current locale or null if the locale currently used is
522      * the default one.
523      */

524     public Locale JavaDoc getLocale() {
525         // <!> TODO : in Rhino the local is for a thread not a scope..
526
return null;
527     }
528
529     /**
530      * Provides a way to the user to specify a locale which override the
531      * default one. If null is passed to this method, the used locale
532      * becomes the global one.
533      * @param locale The locale to set.
534      */

535     public void setLocale(Locale JavaDoc locale) {
536         // <!> TODO : in Rhino the local is for a thread not a scope..
537
}
538
539     /**
540      * Creates and returns a localized message, given the key of the message, 0, data.length
541      * in the resource bundle and the message parameters.
542      * The messages in the resource bundle must have the syntax described in
543      * the java.text.MessageFormat class documentation.
544      * @param key The key used to retreive the message from the resource
545      * bundle.
546      * @param args The objects that compose the message.
547      * @exception MissingResourceException if the key is not in the bundle.
548      */

549     public String JavaDoc formatMessage(String JavaDoc key, Object JavaDoc[] args) {
550         return null;
551     }
552
553     public class ExtendedContext extends Context JavaDoc {
554         public ExtendedContext() {
555             super();
556         }
557
558         public RhinoInterpreter getInterpreter() {
559             return RhinoInterpreter.this;
560         }
561
562         public Window getWindow() {
563             return RhinoInterpreter.this.getWindow();
564         }
565
566         public ScriptableObject getGlobalObject() {
567             return RhinoInterpreter.this.getGlobalObject();
568         }
569     }
570 }
571
Popular Tags