content: package com.alibaba.tamper.process.script.jexl;
  
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import org.apache.commons.jexl2.Expression;
  import org.apache.commons.jexl2.Interpreter;
  import org.apache.commons.jexl2.JexlContext;
  import org.apache.commons.jexl2.JexlEngine;
  import org.apache.commons.jexl2.MapContext;
  import org.apache.commons.jexl2.parser.JexlNode;
  
  import com.alibaba.tamper.process.script.ScriptContext;
  import com.alibaba.tamper.process.script.ScriptExecutor;
  import com.alibaba.tamper.process.script.lifecyle.DisposableScript;
  import com.alibaba.tamper.process.script.lifecyle.InitializingScript;
  
  /**
   * Jexl的script实现
   * 
   * @author jianghang 2011-5-25 下午08:08:45
   */
  public class JexlScriptExecutor implements ScriptExecutor {
  
      private static final int    DEFAULT_CACHE_SIZE = 1000;
      private ScriptJexlEngine    engine;
      private Map<String, Object> functions;
      private int                 cacheSize          = DEFAULT_CACHE_SIZE;
  
      public JexlScriptExecutor(){
          initialize();
      }
  
      /**
       * 初始化function
       */
      public void initialize() {
          if (cacheSize <= 0) {// 不考虑cacheSize为0的情况，强制使用LRU cache机制
              cacheSize = DEFAULT_CACHE_SIZE;
          }
  
          functions = new HashMap<String, Object>();
          engine = new ScriptJexlEngine();
          engine.setCache(cacheSize);
          engine.setSilent(true);
          engine.setFunctions(functions); // 注册functions
      }
  
      public ScriptContext genScriptContext(Map<String, Object> context) {
          return new JexlScriptContext(context);
      }
  
      public Object evaluate(Map<String, Object> context, String script) {
          return evaluate(genScriptContext(context), script);
      }
  
      /**
       * <pre>
       * 1. 接受JexlScriptContext上下文
       * 2. script针对对应name下的script脚本
       * </pre>
       */
      public Object evaluate(ScriptContext context, String script) {
          Expression expr = engine.createExpression(script);
          return expr.evaluate((JexlContext) context);
      }
  
      // ============================ setter / getter ============================
  
      public void setCacheSize(int cacheSize) {
          this.cacheSize = cacheSize;
      }
  
      public void addFunction(String name, Object obj) {
          this.functions.put(name, obj);
      }
  
      public void disposeFunctions() {
          engine.disposeUsedFunctions();
      }
  
  }
  
  /**
   * 定义为Jexl context
   * 
   * @author jianghang 2011-5-25 下午07:57:59
   */
  class JexlScriptContext extends MapContext implements ScriptContext {
  
      public JexlScriptContext(Map map){
          super(map);
      }
  }
  
  /**
   * 扩展jexl的实现，提供{@linkplain InitializingScript},{@linkplain DisposableScript}的管理
   */
  class ScriptJexlEngine extends JexlEngine {
  
      // 记录script执行过程中使用过的function
      private ThreadLocal<Set<Object>> usedFunctions = null;
  
      public ScriptJexlEngine(){
          super(null, null, null, null);
          usedFunctions = new ThreadLocal<Set<Object>>() {
  
              protected Set<Object> initialValue() {
                  return new HashSet<Object>(); // 线程安全，不全同步
              }
          };
      }
  
      protected Interpreter createInterpreter(JexlContext context) {
          if (context == null) {
              context = EMPTY_CONTEXT;
          }
          return new ScriptInterpreter(this, context);
      }
  
      class ScriptInterpreter extends Interpreter {
  
          public ScriptInterpreter(JexlEngine jexl, JexlContext aContext){
              super(jexl, aContext);
          }
  
          protected Object resolveNamespace(String prefix, JexlNode node) {
              Object result = super.resolveNamespace(prefix, node);
              if (result != null) {
                  boolean contains = usedFunctions.get().add(result);// 添加到使用记录中
                  if (contains && InitializingScript.class.isAssignableFrom(result.getClass())) {// 第一次添加
                      ((InitializingScript) result).initial();// 回调
                  }
              }
              return result;
          }
      }
  
      public void disposeUsedFunctions() {
          try {
              Set<Object> functions = usedFunctions.get();
              for (Object function : functions) {
                  if (function != null && DisposableScript.class.isAssignableFrom(function.getClass())) {
                      ((DisposableScript) function).dispose();// 回调
                  }
              }
          } finally {
              usedFunctions.set(new HashSet<Object>());// 清空
          }
      }
  
  }
  