/* GenericInterpreter.java Purpose: Description: History: Thu Feb 8 23:47:29 2007, Created by tomyeh Copyright (C) 2007 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.zk.scripting.util; import java.util.LinkedList; import java.util.List; import org.zkoss.lang.Objects; import org.zkoss.xel.Function; import org.zkoss.zk.scripting.Interpreter; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Execution; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.IdSpace; import org.zkoss.zk.ui.Page; import org.zkoss.zk.ui.ext.Scope; import org.zkoss.zk.ui.ext.Scopes; /** * A skeletal class for implementing a interpreter ({@link Interpreter}). * * <p>Derive classes usually override {@link #exec} instead of {@link #interpret}; * In addition, don't override {@link #getVariable}, * {@link #setVariable} and {@link #unsetVariable}. * Instead, override {@link #get(String)}, {@link #contains(String)}, * {@link #set(String,Object)} and {@link #unset(String)} instead. * * <p>If an interpreter doesn't support hierarchical scopes, * it can simply implement a global scope, and then use * {@link #getFromNamespace} to * retrieve variables from ZK's hierarchical scopes. * * <p>If it supports hierarchical scopes * (example: {@link org.zkoss.zk.scripting.bsh.BSHInterpreter}), it * can maintain a one-to-one relationship among interpreter's scopes * and ZK's {@link IdSpace}. Thus, it can retrieve * the correct scope by giving ZK's {@link IdSpace}, and vice versa. * It also has to implement {@link #get(Scope,String)}, {@link #contains(Scope,String)} * {@link #set(Scope,String,Object)} and {@link #unset(Scope,String)}. * * <p>Whether to support hierarchical scopes is optional. * * @author tomyeh */ public abstract class GenericInterpreter implements Interpreter { /** Used by {@link #getFromNamespace} to denote a variable is not defined. * @since 2.4.0 */ public static final Object UNDEFINED = new Object() { public String toString() { return "undefined"; } }; /** A list of {@link Scope}. * Top of it is the current one (if null, it means Scopes.getCurrent) */ private final List<Object> _scopes = new LinkedList<Object>(); private Page _owner; private String _zslang; protected GenericInterpreter() { } //interface to override// /** Executes the specified script. * Deriving class shall provide an implementation of this method, rather * than overriding {@link #interpret}. */ protected abstract void exec(String script); /** Tests whether a variable is defined in this interpreter. * Optional. Implement it if the interpreter can tell the difference * between null and undefined. * * <p>By default, it tests whether {@link #get(String)} returns non-null. * @since 2.4.0 */ protected boolean contains(String name) { return get(name) != null; } /** Tests whether a variable is defined in the interpreter's scope * associated with the specified scope. * Optional. Implement it if the interpreter can tell the difference * between null and undefined. * * <p>By default, it tests whether {@link #get(Scope, String)} * returns non-null. * @since 5.0.0 */ protected boolean contains(Scope scope, String name) { return get(scope, name) != null; } /** Gets the variable from the interpreter. * Optional. Implement it if you want to expose variables defined * in the interpreter to Java codes. * * <p>{@link #beforeExec} is called first, before this method is invoked. * * <p>An empty (and fake) scope is pushed so {@link #getFromNamespace} * always returns null. */ protected Object get(String name) { return null; } /** Gets the variable from the interpreter's scope associated with * the giving scope. * Optional. Implement it if you want to expose variables defined * in the interpreter to Java codes. * * <p>This method is implemented only if the interpreter that supports * hierachical scopes ({@link org.zkoss.zk.scripting.HierachicalAware}). * * <p>Default: the same as {@link #get(String)}. * * <p>{@link #beforeExec} is called first, before this method is invoked. * * <p>An empty (and fake) scope is pushed so {@link #getFromNamespace} * always returns null. * @since 5.0.0 */ protected Object get(Scope scope, String name) { return get(name); } /** Sets the variable to the interpreter. * Optional. Implement it if you want to allow Java codes to define * a variable in the interpreter. * * <p>{@link #beforeExec} is called first, before this method is invoked. */ protected void set(String name, Object value) { } /** Sets the variable to the interpreter's scope associated with the * giving scope. * Optional. Implement it if you want to allow Java codes to define * a variable in the interpreter. * * <p>This method is implemented only if the interpreter that supports * hierarchical scopes ({@link org.zkoss.zk.scripting.HierachicalAware}). * * <p>Default: the same as {@link #set(String, Object)}. * * <p>{@link #beforeExec} is called first, before this method is invoked. * @since 5.0.0 */ protected void set(Scope scope, String name, Object value) { set(name, value); } /** Removes the variable from the interpreter. * Optional. Implement it if you want to allow Java codes to undefine * a variable from the interpreter. * * <p>{@link #beforeExec} is called first, before this method is invoked. */ protected void unset(String name) { } /** Removes the variable from the interpreter. * Optional. Implement it if you want to allow Java codes to undefine * a variable from the interpreter. * * <p>This method is implemented only if the interpreter that supports * hierarchical scopes ({@link org.zkoss.zk.scripting.HierachicalAware}). * * <p>Default: the same as {@link #unset(String)}. * * <p>{@link #beforeExec} is called first, before this method is invoked. * @since 5.0.0 */ protected void unset(Scope scope, String name) { unset(name); } /** Called before {@link #exec}. * <p>Default: call {@link #beforeExec} and push the scope * as the active one. * @since 5.0.0 */ protected void beforeInterpret(Scope scope) { beforeExec(); push(scope); //getFromNamespace will handle null } /** Called after {@link #exec}. * <p>Default: call {@link #afterExec}. * @since 5.0.0 */ protected void afterInterpret(Scope scope) { pop(); afterExec(); } /** Called before {@link #exec}, {@link #get} and many others. * <p>Default: does nothing. */ protected void beforeExec() { } /** Called after {@link #exec}, {@link #get} and many others. * <p>Default: does nothing. */ protected void afterExec() { } //utilities// /** Returns the variable through scopes and variable resolvers, * or {@link #UNDEFINED} if the variable not defined. * * <p>It is usually called to search scopes and variable resolvers, * when the real interpreter failed to find a variable in its own scope. * * <p>Note: We use {@link #UNDEFINED} to denote undefined since 2.4.0, * while null is a valid value. */ protected Object getFromNamespace(String name) { final Scope scope = getCurrent(); if (scope != null) { //null means no scope allowed! final Execution exec = Executions.getCurrent(); if (exec != null && exec != scope) { Object val = exec.getAttribute(name); if (val != null /*|| exec.hasAttribute(name)*/) //request's hasAttribute same as getAttribute return val; } if (scope instanceof Component) { Component comp = (Component) scope; Object val = comp.getAttributeOrFellow(name, true); if (val != null || comp.hasAttributeOrFellow(name, true)) return val; Page page = comp.getPage(); if (page != null) { val = page.getXelVariable(null, null, name, true); if (val != null) return val; } } else if (scope instanceof Page) { Page page = (Page) scope; Object val = page.getAttributeOrFellow(name, true); if (val != null || page.hasAttributeOrFellow(name, true)) return val; val = page.getXelVariable(null, null, name, true); if (val != null) return val; } else { Object val = scope.getAttribute(name, true); if (val != null || scope.hasAttribute(name, true)) return val; } } return getImplicit(name); } /** Returns the variable through the specified scopes and * variable resolvers, or {@link #UNDEFINED} if the variable is not * defined. * * <p>It is usually called to search scopes and variable resolvers, * when the real interpreter failed to find a variable in its own scope. * * <p>This method is used with the interpreter that supports * hierarchical scopes ({@link org.zkoss.zk.scripting.HierachicalAware}). * * @param scope the scope to search from (never null). * Note: if {@link #getCurrent} returns null, this method simply returns * null (i.e., ignoring scope). * @param recurse whether to look for the parent scope, if any. * @since 5.0.0 */ protected Object getFromNamespace(Scope scope, String name, boolean recurse) { if (getCurrent() != null) { //null means no scope allowed! Object val = scope.getAttribute(name, recurse); if (val != null || scope.hasAttribute(name, recurse)) return val; } return getImplicit(name); } /** Returns the value of the implicit variables. * It is called by {@link #getFromNamespace}, so you don't need to * invoke this method if you invoke {@link #getFromNamespace}. * However, you have to invoke this method as the last step, if you * implement your own getFromNamespace from scratch. * @since 3.6.0 */ protected static Object getImplicit(String name) { return Scopes.getImplicit(name, UNDEFINED); } //Interpreter// public void init(Page owner, String zslang) { _owner = owner; _zslang = zslang; } /** Reset the owner ({@link #getOwner}) to null. */ public void destroy() { _owner = null; } public Page getOwner() { return _owner; } public String getLanguage() { return _zslang; } /** Handles the scope and then invoke {@link #exec}. * <p>Don't override this method, rather, override {@link #exec}. * @since 5.0.0 */ public void interpret(String script, Scope scope) { final String each = _owner.getLanguageDefinition().getEachTimeScript(_zslang); if (each != null) script = each + '\n' + script; beforeInterpret(scope); try { exec(script); } finally { afterInterpret(scope); } } /** Returns null since retrieving class is not supported. */ public Class getClass(String clsnm) { return null; } /** Returns null since retrieving methods is not supported. * @since 3.0.0 */ public Function getFunction(String name, Class[] argTypes) { return null; } /** Returns null since retrieving methods is not supported. * @since 5.0.0 */ public Function getFunction(Scope scope, String name, Class[] argTypes) { return null; } /** Tests whether the variable exists. * * <p>Deriving class shall override {@link #contains(String)}, instead of this method. * @since 2.4.0 */ public boolean containsVariable(String name) { beforeExec(); push(Objects.UNKNOWN); //don't use null since it means Scopes#getCurrent, see below try { return contains(name); } finally { pop(); afterExec(); } } /** Tests whether the variable exists by using the specified name * as a reference to identify the interpreter's scope. * * <p>Deriving class shall override {@link #contains(Scope,String)}, instead of this method. * @since 5.0.0 */ public boolean containsVariable(Scope scope, String name) { beforeExec(); push(Objects.UNKNOWN); //don't use null since it means Scopes#getCurrent, see below try { return contains(scope, name); } finally { pop(); afterExec(); } } /** Retrieve the variable. * * <p>Deriving class shall override {@link #get(String)}, instead of this method. */ public Object getVariable(String name) { beforeExec(); push(Objects.UNKNOWN); //don't use null since it means Scopes#getCurrent, see below try { return get(name); } finally { pop(); afterExec(); } } /** Returns the value of a variable defined in this interpreter's * scope identified by the specified scope. * Note: it doesn't search the specified scope ({@link Scope}). * * <p>Deriving class shall override {@link #get(Scope,String)}, * instead of this method. * * <p>This method is part of {@link org.zkoss.zk.scripting.HierachicalAware}. * It is defined here to simplify the implementation of the * deriving classes, if they support the hierarchical scopes. * @since 5.0.0 */ public Object getVariable(Scope scope, String name) { beforeExec(); push(Objects.UNKNOWN); //don't use null since it means Scopes#getCurrent, see below try { return get(scope, name); } finally { pop(); afterExec(); } } /** Sets the variable to this interpreter. * * <p>Deriving class shall override {@link #set(String,Object)}, instead of this method. */ public final void setVariable(String name, Object value) { beforeExec(); try { set(name, value); } finally { afterExec(); } } /** Sets the value of a variable to this interpreter's scope * identified by the specified scope. * * <p>Deriving class shall override {@link #set(Scope,String,Object)}, * instead of this method. * @since 5.0.0 */ public final void setVariable(Scope scope, String name, Object value) { beforeExec(); try { set(scope, name, value); } finally { afterExec(); } } /** Removes the variable from this interpreter. * * <p>Deriving class shall override {@link #unset(String)}, instead of this method. */ public final void unsetVariable(String name) { beforeExec(); try { unset(name); } finally { afterExec(); } } /** Removes the value of a variable defined in the interpreter's * scope identified by the specified scope. * * <p>Deriving class shall override {@link #unset(Scope,String)}, instead of this method. * @since 5.0.0 */ public final void unsetVariable(Scope scope, String name) { beforeExec(); try { unset(scope, name); } finally { afterExec(); } } /** Use the specified scope as the active scope. */ private void push(Object scope) { _scopes.add(0, scope); } /** Remove the active scope. */ private void pop() { _scopes.remove(0); } /** Returns the current scope, or null if no scope is allowed. * Note: if this method returns null, it means the interpreter shall * not search variables defined in ZK scope. * * <p>This method will handle {@link Scopes#getCurrent} automatically. * @since 5.0.0 */ protected Scope getCurrent() { if (!_scopes.isEmpty()) { Object o = _scopes.get(0); if (o == Objects.UNKNOWN) return null; //no scope allowed if (o != null) return (Scope) o; //we assume owner's scope if null, because zscript might //define a class that will be invoke thru, say, event listener //In other words, interpret is not called, so scope is not specified } return Scopes.getCurrent(_owner); //never null } }