1 /* BSHInterpreter.java
2
3 {{IS_NOTE
4 Purpose:
5
6 Description:
7
8 History:
9 Thu Jun 1 14:28:43 2006, Created by tomyeh
10 }}IS_NOTE
11
12 Copyright (C) 2006 Potix Corporation. All Rights Reserved.
13
14 {{IS_RIGHT
15 This program is distributed under GPL Version 2.0 in the hope that
16 it will be useful, but WITHOUT ANY WARRANTY.
17 }}IS_RIGHT
18 */
19 package org.zkoss.zk.scripting.bsh;
20
21 import java.lang.reflect.Field;
22 import java.util.Iterator;
23 import java.util.Map;
24 import java.util.Collection;
25
26 import bsh.BshClassManager;
27 import bsh.NameSpace;
28 import bsh.BshMethod;
29 import bsh.Variable;
30 import bsh.Primitive;
31 import bsh.EvalError;
32 import bsh.UtilEvalError;
33
34 import org.zkoss.lang.Classes;
35 import org.zkoss.lang.reflect.Fields;
36 import org.zkoss.xel.Function;
37
38 import org.zkoss.zk.ui.Page;
39 import org.zkoss.zk.ui.UiException;
40 import org.zkoss.zk.scripting.Namespace;
41 import org.zkoss.zk.scripting.NamespaceChangeListener;
42 import org.zkoss.zk.scripting.util.GenericInterpreter;
43 import org.zkoss.zk.scripting.SerializableAware;
44 import org.zkoss.zk.scripting.HierachicalAware;
45
46 /**
47 * The interpreter that uses BeanShell to interpret zscript codes.
48 *
49 * <p>Unlike many other implementations, it supports the hierachical
50 * scopes ({@link HierachicalAware}).
51 * That is, it uses an independent BeanShell NameSpace
52 * (aka. interpreter's scope) to store the variables/classes/methods
53 * defined in BeanShell script for each ZK namespace ({@link Namespace}).
54 * Since one-to-one relationship between BeanShell's scope and ZK namespace,
55 * the invocation of BeanShell methods can execute correctly without knowing
56 * what namespace it is.
57 * However, if you want your codes portable across different interpreters,
58 * you had better to call
59 * {@link org.zkoss.zk.scripting.Namespaces#beforeInterpret}
60 * to prepare the proper namespace, before calling any method defined in
61 * zscript.
62 *
63 * @author tomyeh
64 */
65 public class BSHInterpreter extends GenericInterpreter
66 implements SerializableAware, HierachicalAware {
67 /** A variable of {@link Namespace}. The value is an instance of
68 * BeanShell's NameSpace.
69 */
70 private static final String VAR_NS = "z_bshnS";
71 private bsh.Interpreter _ip;
72 private GlobalNS _bshns;
73
74 public BSHInterpreter() {
75 }
76
77 //Deriving to override//
78 /** Called when the top-level BeanShell namespace is created.
79 * By default, it does nothing.
80 *
81 * <p>Note: to speed up the performance, this implementation
82 * disabled {@link bsh.NameSpace#loadDefaultImports}.
83 * It only imports the java.lang and java.util packages.
84 * If you want the built command and import packages, you can override
85 * this method. For example,
86 * <pre><code>
87 *protected void loadDefaultImports(NameSpace bshns) {
88 * bshns.importCommands("/bsh/commands");
89 *}</code></pre>
90 *
91 * @since 3.0.2
92 */
93 protected void loadDefaultImports(NameSpace bshns) {
94 }
95
96 //GenericInterpreter//
97 protected void exec(String script) {
98 try {
99 final Namespace ns = getCurrent();
100 if (ns != null) _ip.eval(script, prepareNS(ns));
101 else _ip.eval(script); //unlikely (but just in case)
102 } catch (EvalError ex) {
103 throw UiException.Aide.wrap(ex);
104 }
105 }
106
107 protected boolean contains(String name) {
108 try {
109 return _ip.getNameSpace().getVariable(name) != Primitive.VOID;
110 //Primitive.VOID means not defined
111 } catch (UtilEvalError ex) {
112 throw UiException.Aide.wrap(ex);
113 }
114 }
115 protected Object get(String name) {
116 try {
117 return Primitive.unwrap(_ip.get(name));
118 } catch (EvalError ex) {
119 throw UiException.Aide.wrap(ex);
120 }
121 }
122 protected void set(String name, Object val) {
123 try {
124 _ip.set(name, val);
125 //unlike NameSpace.setVariable, _ip.set() handles null
126 } catch (EvalError ex) {
127 throw UiException.Aide.wrap(ex);
128 }
129 }
130 protected void unset(String name) {
131 try {
132 _ip.unset(name);
133 } catch (EvalError ex) {
134 throw UiException.Aide.wrap(ex);
135 }
136 }
137
138 protected boolean contains(Namespace ns, String name) {
139 if (ns != null) {
140 final NameSpace bshns = prepareNS(ns);
141 //note: we have to create NameSpace (with prepareNS)
142 //to have the correct chain
143 if (bshns != _bshns) {
144 try {
145 return bshns.getVariable(name) != Primitive.VOID;
146 } catch (UtilEvalError ex) {
147 throw UiException.Aide.wrap(ex);
148 }
149 }
150 }
151 return contains(name);
152 }
153 protected Object get(Namespace ns, String name) {
154 if (ns != null) {
155 final NameSpace bshns = prepareNS(ns);
156 //note: we have to create NameSpace (with prepareNS)
157 //to have the correct chain
158 if (bshns != _bshns) {
159 try {
160 return Primitive.unwrap(bshns.getVariable(name));
161 } catch (UtilEvalError ex) {
162 throw UiException.Aide.wrap(ex);
163 }
164 }
165 }
166 return get(name);
167 }
168 protected void set(Namespace ns, String name, Object val) {
169 if (ns != null) {
170 final NameSpace bshns = prepareNS(ns);
171 //note: we have to create NameSpace (with prepareNS)
172 //to have the correct chain
173 if (bshns != _bshns) {
174 try {
175 bshns.setVariable(
176 name, val != null ? val: Primitive.NULL, false);
177 return;
178 } catch (UtilEvalError ex) {
179 throw UiException.Aide.wrap(ex);
180 }
181 }
182 }
183 set(name, val);
184 }
185 protected void unset(Namespace ns, String name) {
186 if (ns != null) {
187 final NameSpace bshns = prepareNS(ns);
188 //note: we have to create NameSpace (with prepareNS)
189 //to have the correct chain
190 if (bshns != _bshns) {
191 bshns.unsetVariable(name);
192 return;
193 }
194 }
195 unset(name);
196 }
197
198 //-- Interpreter --//
199 public void init(Page owner, String zslang) {
200 super.init(owner, zslang);
201
202 _ip = new bsh.Interpreter();
203 _ip.setClassLoader(Thread.currentThread().getContextClassLoader());
204
205 _bshns = new GlobalNS(_ip.getClassManager(), "global");
206 _ip.setNameSpace(_bshns);
207 }
208 public void destroy() {
209 getOwner().getNamespace().unsetVariable(VAR_NS, false);
210
211 //bug 1814819 ,clear variable, dennis
212 try{
213 _bshns.clear();
214 _ip.setNameSpace(null);
215 } catch (Throwable t) { //silently ignore (in case of upgrading to new bsh)
216 }
217
218 _ip = null;
219 _bshns = null;
220 super.destroy();
221 }
222
223 /** Returns the native interpreter, or null if it is not initialized
224 * or destroyed.
225 * From application's standpoint, it never returns null, and the returned
226 * object must be an instance of {@link bsh.Interpreter}
227 * @since 3.0.2
228 */
229 public Object getNativeInterpreter() {
230 return _ip;
231 }
232
233 public Class getClass(String clsnm) {
234 try {
235 return _bshns.getClass(clsnm);
236 } catch (UtilEvalError ex) {
237 throw new UiException("Failed to load class "+clsnm, ex);
238 }
239 }
240 public Function getFunction(String name, Class[] argTypes) {
241 return getFunction0(_bshns, name, argTypes);
242 }
243 public Function getFunction(Namespace ns, String name, Class[] argTypes) {
244 return getFunction0(prepareNS(ns), name, argTypes);
245 }
246 private Function getFunction0(NameSpace bshns, String name, Class[] argTypes) {
247 try {
248 final BshMethod m = bshns.getMethod(
249 name, argTypes != null ? argTypes: new Class[0], false);
250 return m != null ? new BSHFunction(m): null;
251 } catch (UtilEvalError ex) {
252 throw UiException.Aide.wrap(ex);
253 }
254 }
255
256 /** Prepares the namespace for non-top-level namespace.
257 */
258 private NameSpace prepareNS(Namespace ns) {
259 if (ns == getOwner().getNamespace())
260 return _bshns;
261
262 NSX nsx = (NSX)ns.getVariable(VAR_NS, true);
263 if (nsx != null)
264 return nsx.ns;
265
266 //bind bshns and ns
267 Namespace p = ns.getParent();
268 NameSpace bshns = //Bug 1831534: we have to pass class manager
269 new NS(p != null ? prepareNS(p): _bshns, _ip.getClassManager(), ns);
270 //Bug 1899353: we have to use _bshns instead of null
271 //Reason: unknown
272 ns.setVariable(VAR_NS, new NSX(bshns), true);
273 return bshns;
274 }
275 /** Prepares the namespace for detached components. */
276 private static NameSpace prepareDetachedNS(Namespace ns) {
277 NSX nsx = (NSX)ns.getVariable(VAR_NS, true);
278 if (nsx != null)
279 return nsx.ns;
280
281 //bind bshns and ns
282 Namespace p = ns.getParent();
283 NameSpace bshns = new NS(p != null ? prepareDetachedNS(p): null, null, ns);
284 ns.setVariable(VAR_NS, new NSX(bshns), true);
285 return bshns;
286 }
287
288 //supporting classes//
289 /** The global namespace. */
290 private static abstract class AbstractNS extends NameSpace {
291 private boolean _inGet;
292
293 protected AbstractNS(NameSpace parent, BshClassManager classManager,
294 String name) {
295 super(parent, classManager, name);
296 }
297
298 /** Deriver has to override this method. */
299 abstract protected Object getFromNamespace(String name);
300
301 //super//
302 protected Variable getVariableImpl(String name, boolean recurse)
303 throws UtilEvalError {
304 //Note: getVariableImpl returns null if not defined,
305 //while getVariable return Primitive.VOID if not defined
306
307 //Tom M Yeh: 20060606:
308 //We cannot override getVariable because BeanShell use
309 //getVariableImpl to resolve a variable recusrivly
310 //
311 //setVariable will callback this method,
312 //so use _inGet to prevent dead loop
313 Variable var = super.getVariableImpl(name, recurse);
314 if (!_inGet && var == null) {
315 Object v = getFromNamespace(name);
316 if (v != UNDEFINED) {
317 //Variable has no public/protected contructor, so we have to
318 //store the value back (with setVariable) and retrieve again
319 _inGet = true;
320 try {
321 this.setVariable(name,
322 v != null ? v: Primitive.NULL, false);
323 var = super.getVariableImpl(name, false);
324 this.unsetVariable(name); //restore
325 } finally {
326 _inGet = false;
327 }
328 }
329 }
330 return var;
331 }
332 public void loadDefaultImports() {
333 //to speed up the formance
334 }
335 }
336 /** The global NameSpace. */
337 private class GlobalNS extends AbstractNS {
338 private GlobalNS(BshClassManager classManager,
339 String name) {
340 super(null, classManager, name);
341 }
342 protected Object getFromNamespace(String name) {
343 return BSHInterpreter.this.getFromNamespace(name);
344 }
345 public void loadDefaultImports() {
346 BSHInterpreter.this.loadDefaultImports(this);
347 }
348 }
349 /** The per-Namespace NameSpace. */
350 private static class NS extends AbstractNS {
351 private final Namespace _ns;
352
353 private NS(NameSpace parent, BshClassManager classManager, Namespace ns) {
354 super(parent, classManager, "ns" + System.identityHashCode(ns));
355 _ns = ns;
356 _ns.addChangeListener(new NSCListener(this));
357 }
358
359 //super//
360 /** Search _ns instead. */
361 protected Object getFromNamespace(String name) {
362 final BSHInterpreter ip = getInterpreter();
363 return ip != null ? ip.getFromNamespace(_ns, name):
364 _ns.getVariable(name, false);
365 }
366 private BSHInterpreter getInterpreter() {
367 Page owner = _ns.getOwnerPage();
368 if (owner != null) {
369 for (Iterator it = owner.getLoadedInterpreters().iterator();
370 it.hasNext();) {
371 final Object ip = it.next();
372 if (ip instanceof BSHInterpreter)
373 return (BSHInterpreter)ip;
374 }
375 }
376 return null;
377 }
378 }
379 private static class NSCListener implements NamespaceChangeListener {
380 private final NS _bshns;
381 private NSCListener(NS bshns) {
382 _bshns = bshns;
383 }
384 public void onAdd(String name, Object value) {
385 }
386 public void onRemove(String name) {
387 }
388 public void onParentChanged(Namespace newparent) {
389 if (newparent != null) {
390 final BSHInterpreter ip = _bshns.getInterpreter();
391 _bshns.setParent(
392 ip != null ? ip.prepareNS(newparent):
393 prepareDetachedNS(newparent));
394 return;
395 }
396
397 _bshns.setParent(null);
398 }
399 }
400 /** Non-serializable namespace. It is used to prevent itself from
401 * being serialized
402 */
403 private static class NSX {
404 NameSpace ns;
405 private NSX(NameSpace ns) {
406 this.ns = ns;
407 }
408 }
409
410 //SerializableAware//
411 public void write(java.io.ObjectOutputStream s, Filter filter)
412 throws java.io.IOException {
413 //1. variables
414 final String[] vars = _bshns.getVariableNames();
415 for (int j = vars != null ? vars.length: 0; --j >= 0;) {
416 final String nm = vars[j];
417 if (nm != null && !"bsh".equals(nm)) {
418 final Object val = get(nm);
419 if ((val == null || (val instanceof java.io.Serializable)
420 || (val instanceof java.io.Externalizable))
421 && (filter == null || filter.accept(nm, val))) {
422 s.writeObject(nm);
423 s.writeObject(val);
424 }
425 }
426 }
427 s.writeObject(null); //denote end-of-vars
428
429 //2. methods
430 final BshMethod[] mtds = _bshns.getMethods();
431 for (int j = mtds != null ? mtds.length: 0; --j >= 0;) {
432 final String nm = mtds[j].getName();
433 if (filter == null || filter.accept(nm, mtds[j])) {
434 //hack BeanShell 2.0b4 which cannot be serialized correctly
435 Field f = null;
436 boolean acs = false;
437 try {
438 f = Classes.getAnyField(BshMethod.class, "declaringNameSpace");
439 acs = f.isAccessible();
440 Fields.setAccessible(f, true);
441 final Object old = f.get(mtds[j]);
442 try {
443 f.set(mtds[j], null);
444 s.writeObject(mtds[j]);
445 } finally {
446 f.set(mtds[j], old);
447 }
448 } catch (java.io.IOException ex) {
449 throw ex;
450 } catch (Throwable ex) {
451 throw UiException.Aide.wrap(ex);
452 } finally {
453 if (f != null) Fields.setAccessible(f, acs);
454 }
455 }
456 }
457 s.writeObject(null); //denote end-of-mtds
458
459 //3. imported class
460 Field f = null;
461 boolean acs = false;
462 try {
463 f = Classes.getAnyField(NameSpace.class, "importedClasses");
464 acs = f.isAccessible();
465 Fields.setAccessible(f, true);
466 final Map clses = (Map)f.get(_bshns);
467 if (clses != null)
468 for (Iterator it = clses.values().iterator(); it.hasNext();) {
469 final String clsnm = (String)it.next();
470 if (!clsnm.startsWith("bsh."))
471 s.writeObject(clsnm);
472 }
473 } catch (java.io.IOException ex) {
474 throw ex;
475 } catch (Throwable ex) {
476 throw UiException.Aide.wrap(ex);
477 } finally {
478 if (f != null) Fields.setAccessible(f, acs);
479 }
480 s.writeObject(null); //denote end-of-cls
481
482 //4. imported package
483 f = null;
484 acs = false;
485 try {
486 f = Classes.getAnyField(NameSpace.class, "importedPackages");
487 acs = f.isAccessible();
488 Fields.setAccessible(f, true);
489 final Collection pkgs = (Collection)f.get(_bshns);
490 if (pkgs != null)
491 for (Iterator it = pkgs.iterator(); it.hasNext();) {
492 final String pkgnm = (String)it.next();
493 if (!pkgnm.startsWith("java.awt")
494 && !pkgnm.startsWith("javax.swing"))
495 s.writeObject(pkgnm);
496 }
497 } catch (java.io.IOException ex) {
498 throw ex;
499 } catch (Throwable ex) {
500 throw UiException.Aide.wrap(ex);
501 } finally {
502 if (f != null) Fields.setAccessible(f, acs);
503 }
504 s.writeObject(null); //denote end-of-cls
505 }
506 public void read(java.io.ObjectInputStream s)
507 throws java.io.IOException, ClassNotFoundException {
508 for (;;) {
509 final String nm = (String)s.readObject();
510 if (nm == null) break; //no more
511
512 set(nm, s.readObject());
513 }
514
515 try {
516 for (;;) {
517 final BshMethod mtd = (BshMethod)s.readObject();
518 if (mtd == null) break; //no more
519
520 //fix declaringNameSpace
521 Field f = null;
522 boolean acs = false;
523 try {
524 f = Classes.getAnyField(BshMethod.class, "declaringNameSpace");
525 acs = f.isAccessible();
526 Fields.setAccessible(f, true);
527 f.set(mtd, _bshns);
528 } catch (Throwable ex) {
529 throw UiException.Aide.wrap(ex);
530 } finally {
531 if (f != null) Fields.setAccessible(f, acs);
532 }
533
534 _bshns.setMethod(mtd.getName(), mtd);
535 }
536 } catch (UtilEvalError ex) {
537 throw UiException.Aide.wrap(ex);
538 }
539
540 for (;;) {
541 final String nm = (String)s.readObject();
542 if (nm == null) break; //no more
543
544 _bshns.importClass(nm);
545 }
546
547 for (;;) {
548 final String nm = (String)s.readObject();
549 if (nm == null) break; //no more
550
551 _bshns.importPackage(nm);
552 }
553 }
554
555 private class BSHFunction implements Function {
556 private final bsh.BshMethod _method;
557 private BSHFunction(bsh.BshMethod method) {
558 if (method == null)
559 throw new IllegalArgumentException("null");
560 _method = method;
561 }
562
563 //-- Function --//
564 public Class[] getParameterTypes() {
565 return _method.getParameterTypes();
566 }
567 public Class getReturnType() {
568 return _method.getReturnType();
569 }
570 public Object invoke(Object obj, Object[] args) throws Exception {
571 return _method.invoke(args != null ? args: new Object[0], _ip);
572 }
573 public java.lang.reflect.Method toMethod() {
574 return null;
575 }
576 }
577 }