/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: Redistributions of source code * must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. Neither the name of the Sun Microsystems nor the names of * is contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.l2jserver.commons.javaengine; import java.io.IOException; import java.io.Reader; import java.io.Serializable; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.script.AbstractScriptEngine; import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; import javax.script.ScriptException; import javax.script.SimpleBindings; /** * This is script engine for Java programming language. * @author A. Sundararajan */ public class JavaScriptEngine extends AbstractScriptEngine implements Compilable { // Java compiler private final JavaCompiler compiler; public JavaScriptEngine() { compiler = new JavaCompiler(); } // my factory, may be null private ScriptEngineFactory factory; // my implementation for CompiledScript private static class JavaCompiledScript extends CompiledScript implements Serializable { private final transient JavaScriptEngine _engine; private transient Class _class; private final Map _classBytes; private final String _classPath; JavaCompiledScript(JavaScriptEngine engine, Map classBytes, String classPath) { _engine = engine; _classBytes = classBytes; _classPath = classPath; } @Override public ScriptEngine getEngine() { return _engine; } @Override public Object eval(ScriptContext ctx) throws ScriptException { if (_class == null) { Map classBytesCopy = new HashMap<>(); classBytesCopy.putAll(_classBytes); MemoryClassLoader loader = new MemoryClassLoader(classBytesCopy, _classPath, JavaScriptEngine.getParentLoader(ctx)); _class = JavaScriptEngine.parseMain(loader, ctx); } return JavaScriptEngine.evalClass(_class, ctx); } } @Override public CompiledScript compile(String script) throws ScriptException { return compile(script, context); } @Override public CompiledScript compile(Reader reader) throws ScriptException { return compile(readFully(reader)); } @Override public Object eval(String str, ScriptContext ctx) throws ScriptException { Class clazz = parse(str, ctx); return evalClass(clazz, ctx); } @Override public Object eval(Reader reader, ScriptContext ctx) throws ScriptException { return eval(readFully(reader), ctx); } @Override public ScriptEngineFactory getFactory() { synchronized (this) { if (factory == null) { factory = new JavaScriptEngineFactory(); } } return factory; } @Override public Bindings createBindings() { return new SimpleBindings(); } void setFactory(ScriptEngineFactory factory) { this.factory = factory; } // Internals only below this point private Class parse(String str, ScriptContext ctx) throws ScriptException { String fileName = getFileName(ctx); String sourcePath = getSourcePath(ctx); String classPath = getClassPath(ctx); Writer err = ctx.getErrorWriter(); if (err == null) { err = new StringWriter(); } Map classBytes = compiler.compile(fileName, str, err, sourcePath, classPath); if (classBytes == null) { if (err instanceof StringWriter) { throw new ScriptException(((StringWriter) err).toString()); } throw new ScriptException("compilation failed"); } // create a ClassLoader to load classes from MemoryJavaFileManager MemoryClassLoader loader = new MemoryClassLoader(classBytes, classPath, getParentLoader(ctx)); return parseMain(loader, ctx); } protected static Class parseMain(MemoryClassLoader loader, ScriptContext ctx) throws ScriptException { String mainClassName = getMainClassName(ctx); if (mainClassName != null) { try { Class clazz = loader.load(mainClassName); Method mainMethod = findMainMethod(clazz); if (mainMethod == null) { throw new ScriptException("no main method in " + mainClassName); } return clazz; } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); throw new ScriptException(cnfe); } } // no main class configured - load all compiled classes Iterable> classes; try { classes = loader.loadAll(); } catch (ClassNotFoundException exp) { throw new ScriptException(exp); } // search for class with main method Class c = findMainClass(classes); if (c != null) { return c; } // if class with "main" method, then // return first class Iterator> itr = classes.iterator(); if (itr.hasNext()) { return itr.next(); } return null; } private JavaCompiledScript compile(String str, ScriptContext ctx) throws ScriptException { String fileName = getFileName(ctx); String sourcePath = getSourcePath(ctx); String classPath = getClassPath(ctx); Writer err = ctx.getErrorWriter(); if (err == null) { err = new StringWriter(); } Map classBytes = compiler.compile(fileName, str, err, sourcePath, classPath); if (classBytes == null) { if (err instanceof StringWriter) { throw new ScriptException(((StringWriter) err).toString()); } throw new ScriptException("compilation failed"); } return new JavaCompiledScript(this, classBytes, classPath); } private static Class findMainClass(Iterable> classes) { // find a public class with public static main method for (Class clazz : classes) { int modifiers = clazz.getModifiers(); if (Modifier.isPublic(modifiers)) { Method mainMethod = findMainMethod(clazz); if (mainMethod != null) { return clazz; } } } // okay, try to find package private class that // has public static main method for (Class clazz : classes) { Method mainMethod = findMainMethod(clazz); if (mainMethod != null) { return clazz; } } // no main class found! return null; } // find public static void main(String[]) method, if any private static Method findMainMethod(Class clazz) { try { Method mainMethod = clazz.getMethod("main", new Class[] { String[].class }); int modifiers = mainMethod.getModifiers(); if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) { return mainMethod; } } catch (NoSuchMethodException nsme) { } return null; } // find public static void setScriptContext(ScriptContext) method, if any private static Method findSetScriptContextMethod(Class clazz) { try { Method setCtxMethod = clazz.getMethod("setScriptContext", new Class[] { ScriptContext.class }); int modifiers = setCtxMethod.getModifiers(); if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) { return setCtxMethod; } } catch (NoSuchMethodException nsme) { } return null; } private static String getFileName(ScriptContext ctx) { int scope = ctx.getAttributesScope("javax.script.filename"); if (scope != -1) { return ctx.getAttribute("javax.script.filename", scope).toString(); } return "$unnamed.java"; } // for certain variables, we look for System properties. This is // the prefix used for such System properties private static final String SYSPROP_PREFIX = "com.sun.script.java."; private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final String ARGUMENTS = "arguments"; private static String[] getArguments(ScriptContext ctx) { int scope = ctx.getAttributesScope(ARGUMENTS); if (scope != -1) { Object obj = ctx.getAttribute(ARGUMENTS, scope); if (obj instanceof String[]) { return (String[]) obj; } } // return zero length array return EMPTY_STRING_ARRAY; } private static final String SOURCEPATH = "sourcepath"; private static String getSourcePath(ScriptContext ctx) { int scope = ctx.getAttributesScope(SOURCEPATH); if (scope != -1) { return ctx.getAttribute(SOURCEPATH).toString(); } // look for "com.sun.script.java.sourcepath" return System.getProperty(SYSPROP_PREFIX + SOURCEPATH); } private static final String CLASSPATH = "classpath"; private static String getClassPath(ScriptContext ctx) { int scope = ctx.getAttributesScope(CLASSPATH); if (scope != -1) { return ctx.getAttribute(CLASSPATH).toString(); } // look for "com.sun.script.java.classpath" String res = System.getProperty(SYSPROP_PREFIX + CLASSPATH); if (res == null) { res = System.getProperty("java.class.path"); } return res; } private static final String MAINCLASS = "mainClass"; private static String getMainClassName(ScriptContext ctx) { int scope = ctx.getAttributesScope(MAINCLASS); if (scope != -1) { return ctx.getAttribute(MAINCLASS).toString(); } // look for "com.sun.script.java.mainClass" return System.getProperty("com.sun.script.java.mainClass"); } private static final String PARENTLOADER = "parentLoader"; protected static ClassLoader getParentLoader(ScriptContext ctx) { int scope = ctx.getAttributesScope(PARENTLOADER); if (scope != -1) { Object loader = ctx.getAttribute(PARENTLOADER); if (loader instanceof ClassLoader) { return (ClassLoader) loader; } } return ClassLoader.getSystemClassLoader(); } protected static Object evalClass(Class clazz, ScriptContext ctx) throws ScriptException { // JSR-223 requirement ctx.setAttribute("context", ctx, 100); if (clazz == null) { return null; } try { boolean isPublicClazz = Modifier.isPublic(clazz.getModifiers()); // find the setScriptContext method Method setCtxMethod = findSetScriptContextMethod(clazz); // call setScriptContext and pass current ctx variable if (setCtxMethod != null) { if (!isPublicClazz) { // try to relax access setCtxMethod.setAccessible(true); } setCtxMethod.invoke(null, new Object[] { ctx }); } // find the main method Method mainMethod = findMainMethod(clazz); if (mainMethod != null) { if (!isPublicClazz) { // try to relax access mainMethod.setAccessible(true); } // get "command line" args for the main method String args[] = getArguments(ctx); // call main method mainMethod.invoke(null, new Object[] { args }); } // return main class as eval's result return clazz; } catch (Exception exp) { exp.printStackTrace(); throw new ScriptException(exp); } } // read a Reader fully and return the content as string private String readFully(Reader reader) throws ScriptException { char[] arr = new char[8 * 1024]; // 8K at a time StringBuilder buf = new StringBuilder(); int numChars; try { while ((numChars = reader.read(arr, 0, arr.length)) > 0) { buf.append(arr, 0, numChars); } } catch (IOException exp) { throw new ScriptException(exp); } return buf.toString(); } }