org.siphon.common.js.JsEngineUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.siphon.common.js.JsEngineUtil.java

Source

/*******************************************************************************
 * The MIT License (MIT)
 * Copyright  2015 Inshua,inshua@gmail.com, All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the Software?), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED AS IS?, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *******************************************************************************/
package org.siphon.common.js;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;

import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

import jdk.nashorn.api.scripting.NashornScriptEngine;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.objects.NativeArray;
import jdk.nashorn.internal.objects.NativeError;
import jdk.nashorn.internal.runtime.ECMAException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;

public class JsEngineUtil {

    private final static String importsFn = "/*******************************************************************************\n"
            + " * The MIT License (MIT)\n" + " * Copyright  2015 Inshua,inshua@gmail.com, All rights reserved.\n"
            + " *\n"
            + " * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and\n"
            + " * associated documentation files (the Software?), to deal in the Software without restriction,\n"
            + " * including without limitation the rights to use, copy, modify, merge, publish, distribute,\n"
            + " * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is\n"
            + " * furnished to do so, subject to the following conditions:\n" + " *\n"
            + " * The above copyright notice and this permission notice shall be included in all copies or substantial\n"
            + " * portions of the Software.\n" + " *\n"
            + " * THE SOFTWARE IS PROVIDED AS IS?, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n"
            + " * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
            + " * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES\n"
            + " * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n"
            + " * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
            + " *******************************************************************************/\n"
            + "if(typeof println == 'undefined') engine.put('println', print);\n" + "/**\n"
            + " * ??????\n"
            + " * js??????\n"
            + " * java eval  engine['IMPORTS_PATH_STACK'] ?? IMPORTS_PATH_STACK.last() ??\n"
            + " * java eval \"a.js\" ? ['a.js']\n"
            + " *        a.js ? b/b1.js  './b/b1.js'? ['a.js', 'b/b1.js']\n"
            + " *           b1.js  b/b2.js? imports('b2.js')?? b/b1.js??\n"
            + " *           import(b/b2.js) ?? ['a.js', 'b/b1.js', 'b/b2.js']\n"
            + " *           b2.js eval ???['a.js', 'b/b1.js']\n"
            + " *        b1.js eval ??? ['a.js']\n"
            + " *       a.js ??\n" + " * \n"
            + " * ????? DEFAULT_IMPORTS_PATHS  \n"
            + " * ? ['WEB-INF/jslib/']\n" + " * ??? ['js/', 'jslib/']\n"
            + " * @param scriptFile\n" + " */\n" + "function imports(scriptFile){\n"
            + "   var stk = IMPORTS_PATH_STACK;\n" + "   var last = stk[stk.length-1];\n"
            + "   var currFile = new java.io.File(last);\n"
            + "   var abspath = new java.io.File(currFile, '../' + scriptFile).getCanonicalPath();\n"
            + "   var result;\n" + "   if(new java.io.File(abspath).exists()){\n" + "      stk.push(abspath);\n"
            + "      try{\n" + "         if(!IMPORTED_FILES[abspath]){\n"
            + "            //println('include ' + abspath + ' found at stack');\n"
            + "            IMPORTED_FILES[abspath] = true;\n"
            + "            engine.put(\"javax.script.filename\", abspath);\n"
            + "            result = engine.eval(new java.io.InputStreamReader(new java.io.FileInputStream(abspath), \"utf-8\"));\n"
            + "         }\n" + "      } catch(e){\n" + "         throw e;\n" + "      } finally {\n"
            + "         stk.pop();\n" + "      }\n" + "      return result;\n" + "   } else {\n"
            + "      var defaults = DEFAULT_IMPORTS_PATHS;\n" + "      for(var i=0; i<defaults.length; i++){\n"
            + "         var file = new java.io.File(defaults[i]);\n"
            + "         abspath = new java.io.File(file, scriptFile).getCanonicalPath();\n"
            + "         if(new java.io.File(abspath).exists()){\n" + "            stk.push(abspath);\n"
            + "            try{\n" + "               if(!IMPORTED_FILES[abspath]){\n"
            + "                  //println('include ' + abspath + ' found at default path');\n"
            + "                  IMPORTED_FILES[abspath] = true;\n"
            + "                  engine.put(\"javax.script.filename\", scriptFile);\n"
            + "                  result = engine.eval(new java.io.InputStreamReader(new java.io.FileInputStream(abspath), \"utf-8\"));\n"
            + "               }\n" + "            } catch(e){\n" + "               throw e;\n"
            + "            } finally {\n" + "               stk.pop();\n" + "            }\n"
            + "            return result;\n" + "         }\n" + "      }\n"
            + "      println(scriptFile + ' not found, imports failed, default paths: ' + DEFAULT_IMPORTS_PATHS + ' from: ' + last);\n"
            + "      return result;\n" + "   }\n" + "}   \n" + "/**\n"
            + " *  imports ????\n" + " * @param scriptFile\n" + " */\n"
            + "function include(scriptFile){\n" + "   var stk = IMPORTS_PATH_STACK;\n"
            + "   var last = stk[stk.length-1];\n" + "   var currFile = new java.io.File(last);\n"
            + "   var abspath = new java.io.File(currFile, '../' + scriptFile).getCanonicalPath();\n"
            + "   var result;\n" + "   if(new java.io.File(abspath).exists()){\n" + "      stk.push(abspath);\n"
            + "      try{\n" + "         //println('include ' + abspath + ' found at stack');\n"
            + "         IMPORTED_FILES[abspath] = true;\n"
            + "         engine.put(\"javax.script.filename\", scriptFile);\n"
            + "         result = engine.eval(new java.io.InputStreamReader(new java.io.FileInputStream(abspath), \"utf-8\"));\n"
            + "      } catch(e){\n" + "         throw e;\n" + "      } finally {\n" + "         stk.pop();\n"
            + "      }\n" + "      return result;\n" + "   } else {\n"
            + "      var defaults = DEFAULT_IMPORTS_PATHS;\n" + "      for(var i=0; i<defaults.length; i++){\n"
            + "         var file = new java.io.File(defaults[i]);\n"
            + "         abspath = new java.io.File(file, scriptFile).getCanonicalPath();\n"
            + "         if(new java.io.File(abspath).exists()){\n" + "            stk.push(abspath);\n"
            + "            //println('include ' + abspath + ' found at default path');\n" + "            try{\n"
            + "               IMPORTED_FILES[abspath] = true;\n"
            + "               engine.put(\"javax.script.filename\", abspath);\n"
            + "               result = engine.eval(new java.io.InputStreamReader(new java.io.FileInputStream(abspath), \"utf-8\"));\n"
            + "            } catch(e){\n" + "               throw e;\n" + "            } finally {\n"
            + "               stk.pop();\n" + "            }\n" + "            return result;\n" + "         }\n"
            + "      }\n"
            + "      println(scriptFile + ' not found, include failed, default paths: ' + DEFAULT_IMPORTS_PATHS + ' from: ' + last);\n"
            + "    return result;\n" + "  }\n" + "}\n" + "/**\n"
            + " * ??????eval? include  imports\n"
            + " * @param filename\n" + " */\n" + "function findResource(filename){\n"
            + "  var stk = IMPORTS_PATH_STACK;\n" + "  var last = stk[stk.length-1];\n"
            + "  var currFile = new java.io.File(last);\n" + "  if(new java.io.File(filename).exists()){\n"
            + "    return filename;\n" + "  }\n"
            + "  var abspath = new java.io.File(currFile, '../' + filename).getCanonicalPath();\n"
            + "  var result;\n" + "  if(new java.io.File(abspath).exists()){\n" + "    return abspath;\n"
            + "  } else {\n" + "    var defaults = DEFAULT_IMPORTS_PATHS;\n"
            + "    for(var i=0; i<defaults.length; i++){\n" + "      var file = new java.io.File(defaults[i]);\n"
            + "      abspath = new java.io.File(file, filename).getCanonicalPath();\n"
            + "      if(new java.io.File(abspath).exists()){\n" + "        return abspath;\n" + "      }\n"
            + "    }\n" + "  }\n" + "}\n" + "/**\n" + " * JSON????\n"
            + " * usage: JSON.parse(string, parseDate)\n" + " * @param key\n" + " * @param value\n"
            + " * @returns\n" + " */\n" + "function parseDate(key, value) {\n" + "    switch(typeof value){\n"
            + "    case 'string':\n" + "        var a = parseDate.reg.exec(value);\n" + "        if (a) {\n"
            + "            return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[5] || 0, a[6] || 0, +a[7] || 0));\n"
            + "        }\n" + "        break;\n" + "    }\n" + "    return value;\n" + "}\n"
            + "parseDate.reg = /^(\\d{4})-(\\d{2})-(\\d{2})(T(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d*)?)Z?)?$/;\n"
            + "engine.put('parseDate', parseDate);\n" + "// nashorn \n" + "///**\n"
            + "// *  engine ?\n"
            + "// * ? try catch(e){throw e} ?\n"
            + "// * try{\n" + "// *     (function(){}).invoke()\n" + "// * } catch(e){throw e}\n" + "// */\n"
            + "//Function.prototype.invoke = function(scope){\n"
            + "//  engine.invokeMethod(this, \"call\", scope);\n" + "//}";

    /**
     * 
     * @param jsEngine
     * @param libs [path, path, ...]
     */
    public static void initEngine(ScriptEngine jsEngine, Object[] libs) {
        try {
            jsEngine.put("engine", jsEngine);
            NashornScriptEngine nashornScriptEngine = (NashornScriptEngine) jsEngine;
            JsTypeUtil jsTypeUtil = new JsTypeUtil(jsEngine);
            ScriptObjectMirror importedFiles = jsTypeUtil.newObject();
            jsEngine.put("IMPORTED_FILES", importedFiles);

            ScriptObjectMirror stk = jsTypeUtil.newArray();
            jsEngine.put("IMPORTS_PATH_STACK", stk);

            ScriptObjectMirror defaults = (libs.length > 0 && libs[0] != null && libs[0] instanceof String
                    && ((String) libs[0]).length() > 0) ? jsTypeUtil.newArray(libs) : jsTypeUtil.newArray();
            jsEngine.put("DEFAULT_IMPORTS_PATHS", defaults);

            jsEngine.put(ScriptEngine.FILENAME, "common/engine.js");
            jsEngine.eval(importsFn);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }

    public static ScriptEngine newEngine() {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
        initEngine(engine, new Object[] {});
        return engine;
    }

    public static Object eval(ScriptEngine jsEngine, String srcFile) throws ScriptException, IOException {
        jsEngine.put(ScriptEngine.FILENAME, srcFile);
        return jsEngine.eval(FileUtils.readFileToString(new File(srcFile)));
    }

    public static Object eval(ScriptEngine jsEngine, String srcFile, String code) throws ScriptException {

        jsEngine.put(ScriptEngine.FILENAME, srcFile);
        return jsEngine.eval(code);
    }

    public static Object eval(ScriptEngine jsEngine, String srcFile, boolean onlyOnce, boolean preservePathInStack)
            throws Exception {
        ScriptObjectMirror importedFiles = (ScriptObjectMirror) jsEngine.get("IMPORTED_FILES");
        if (importedFiles.containsKey(srcFile)) {
            if (onlyOnce)
                return null;
        } else {
            importedFiles.put(srcFile, true);
        }
        ScriptObjectMirror stk = (ScriptObjectMirror) jsEngine.get("IMPORTS_PATH_STACK");
        // NativeArray.pushObject(stk.to(NativeArray.class), srcFile);
        //stk.callMember("push", srcFile);

        try {
            String code = FileUtils.readFileToString(new File(srcFile), "utf-8");
            return eval(jsEngine, srcFile, code);
        } catch (ScriptException | FileNotFoundException e) {
            throw e;
        } finally {
            if (!preservePathInStack)
                NativeArray.pop(stk.to(NativeArray.class));
        }
    }

    public static Object eval(ScriptEngine jsEngine, String srcFile, String script, boolean preservePathInStack)
            throws NoSuchMethodException, ScriptException {
        return eval(jsEngine, srcFile, srcFile, script, false, preservePathInStack);
    }

    public static Object eval(ScriptEngine jsEngine, String srcFile, String aliasPath, String script,
            boolean onlyOnce, boolean preservePathInStack) throws NoSuchMethodException, ScriptException {
        ScriptObjectMirror importedFiles = (ScriptObjectMirror) jsEngine.get("IMPORTED_FILES");
        if (importedFiles.containsKey(srcFile)) {
            if (onlyOnce)
                return null;
        } else {
            importedFiles.put(srcFile, true);
        }
        ScriptObjectMirror stk = (ScriptObjectMirror) jsEngine.get("IMPORTS_PATH_STACK");
        //NativeArray.pushObject((Object)(stk.to(NativeArray.class)), (Object)srcFile);
        stk.callMember("push", srcFile);

        try {
            return eval(jsEngine, aliasPath, script);
        } catch (ScriptException e) {
            throw e;
        } finally {
            if (!preservePathInStack)
                NativeArray.pop(stk.to(NativeArray.class));
        }
    }

    /**
     * ? js new Error new   ? js  java ? 
     * @return  js  name  value  Scriptable java   throwable
     */
    public static Object parseJsException(Throwable e) {
        for (Throwable throwable : ExceptionUtils.getThrowables(e)) {
            if (throwable instanceof ECMAException) {
                ECMAException je = (ECMAException) throwable;
                if (je.thrown != null) {
                    Object thrown = je.thrown;
                    if (thrown instanceof Throwable) {
                        return parseJsException((Throwable) thrown);
                    } else if (thrown instanceof NativeError) {
                        return thrown;
                        //                  Object nashornException = ((NativeError) thrown).nashornException;
                        //                  if (nashornException != null) {
                        //                     if (nashornException instanceof Throwable) {
                        //                        return parseJsException((Throwable) nashornException);
                        //                     } else {
                        //                        return nashornException;
                        //                     }
                        //                  } else {
                        //                     return thrown;
                        //                  }
                    } else {
                        return thrown;
                    }
                }
            }
        }
        return e;
    }

    public static Object invokeMethodCrossEngine(ScriptEngine masterEngine, ScriptEngine serverEngine,
            Object object, String method, Object[] arguments) throws NoSuchMethodException, ScriptException {
        if (masterEngine == serverEngine) {
            return ((Invocable) serverEngine).invokeMethod(object, method, arguments);
        } else {
            Object[] arguments2 = new Object[arguments.length];
            for (int i = 0; i < arguments.length; i++) {
                if (arguments[i] instanceof ScriptObjectMirror) {
                    arguments2[i] = ((ScriptObjectMirror) arguments[i]).to(Object.class);
                } else {
                    arguments2[i] = arguments[i];
                }
            }
            Object res = null;
            if (object instanceof ScriptObjectMirror) {
                res = ((ScriptObjectMirror) object).callMember(method, arguments2);
            } else {
                res = ((Invocable) serverEngine).invokeMethod(object, method, arguments2);
            }
            if (res instanceof ScriptObjectMirror) {
                return ((ScriptObjectMirror) res).to(Object.class);
            } else {
                return res;
            }
        }
    }

    public static Object getGlobal(ScriptEngine engine) {
        ScriptContext context = engine.getContext();
        ScriptObjectMirror binding = (ScriptObjectMirror) context.getBindings(ScriptContext.ENGINE_SCOPE);
        Field globalField = null;
        try {
            globalField = ScriptObjectMirror.class.getDeclaredField("global");
            globalField.setAccessible(true);
            Object global = globalField.get(binding);
            return global;
        } catch (NoSuchFieldException e) {
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
        return null;
    }

    public static Object getGlobal(ScriptObjectMirror scriptObjectMirror) {
        Field globalField = null;
        try {
            globalField = ScriptObjectMirror.class.getDeclaredField("global");
            globalField.setAccessible(true);
            Object global = globalField.get(scriptObjectMirror);
            return global;
        } catch (NoSuchFieldException e) {
        } catch (SecurityException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
        return null;
    }

}