/*
* Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.visage.tools.script;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.*;
import java.util.*;
import javax.script.*;
import javax.tools.*;
import com.sun.tools.mjavac.util.Name;
import org.visage.api.VisageScriptEngine;
import com.sun.tools.mjavac.code.*;
/**
* This is script engine for the Visage language, based on
* the https://scripting.dev.java.net Java language script engine by
* A. Sundararajan.
*/
public class VisageScriptEngineImpl extends AbstractScriptEngine
implements VisageScriptEngine {
public VisageScriptEngineImpl() {
}
// my factory, may be null
private ScriptEngineFactory factory;
WeakHashMap<Bindings, VisageScriptContext> contextMap =
new WeakHashMap<Bindings, VisageScriptContext>();
VisageScriptContext getVisageScriptContext(ScriptContext ctx) {
Bindings bindings = ctx.getBindings(ScriptContext.ENGINE_SCOPE);
return getVisageScriptContext(bindings);
}
VisageScriptContext getVisageScriptContext(Bindings bindings) {
VisageScriptContext scontext = contextMap.get(bindings);
if (scontext == null) {
scontext = new VisageScriptContext(Thread.currentThread().getContextClassLoader());
contextMap.put(bindings, scontext);
}
return scontext;
}
// my implementation for CompiledScript
private class VisageScriptCompiledScript extends CompiledScript {
VisageCompiledScript compiled;
VisageScriptCompiledScript(VisageCompiledScript compiled) {
this.compiled = compiled;
}
public VisageScriptEngineImpl getEngine() {
return VisageScriptEngineImpl.this;
}
public Object eval(ScriptContext ctx) throws ScriptException {
VisageScriptContext scontext = getVisageScriptContext(ctx);
try {
// FIXME - set to false if using (unimplemented) "synchronized"
// implementation of ScriptContext and ScriptBindings.
boolean copyVars = true;
if (copyVars) {
Bindings globals = ctx.getBindings(ScriptContext.GLOBAL_SCOPE);
if (globals != null) {
for (Map.Entry<String, Object> entry : globals.entrySet()) {
String key = entry.getKey();
if (key.indexOf('.') >= 0)
continue; // Kludge FIXME
Symbol sym = compiled.lookup(key);
if (compiled.scriptScope.lookup(sym.name).sym == sym)
continue;
scontext.setVarValue(sym, entry.getValue());
}
}
for (Map.Entry<String, Object> entry : ctx.getBindings(ScriptContext.ENGINE_SCOPE).entrySet()) {
String key = entry.getKey();
if (key.indexOf('.') >= 0)
continue; // Kludge FIXME
Symbol sym = compiled.lookup(key);
if (sym == null)
continue;
if (compiled.scriptScope.lookup(sym.name).sym == sym)
continue;
scontext.setVarValue(sym, entry.getValue());
}
}
Object result = compiled.eval(scontext);
if (copyVars) {
for (Scope.Entry e = compiled.compiler.namedImportScope.elems;
e != null; e = e.sibling) {
if ((e.sym.flags() & Flags.SYNTHETIC) != 0)
continue;
String name = e.sym.toString();
if (! (e.sym.owner instanceof Symbol.ClassSymbol))// FIXME - need flag for non-imports.
continue;
if (! (e.sym instanceof Symbol.VarSymbol))// FIXME - need flag for non-imports.
continue;
Object value = scontext.getVarValue(e.sym);
ctx.setAttribute(name, value, ScriptContext.ENGINE_SCOPE);
}
}
return result;
} catch (RuntimeException exp) {
throw exp;
} catch (Error exp) {
throw exp;
} catch (Throwable exp) {
throw new ScriptException((Exception) exp);
}
}
public String getName() {
return compiled.clazzName;
}
}
public CompiledScript compile(String script) throws ScriptException {
return compile(script, null);
}
public CompiledScript compile(Reader reader) throws ScriptException {
return compile(readFully(reader));
}
public CompiledScript compile(String script, DiagnosticListener<JavaFileObject> listener)
throws ScriptException {
return parse(script, context, listener);
}
public CompiledScript compile(Reader script, DiagnosticListener<JavaFileObject> listener)
throws ScriptException {
return compile(readFully(script), listener);
}
public Object eval(String script, DiagnosticListener<JavaFileObject> listener)
throws ScriptException {
return eval(script, getContext(), listener);
}
public Object eval(Reader script, DiagnosticListener<JavaFileObject> listener) throws ScriptException {
return eval(script, getContext(), listener);
}
public Object eval(String str, ScriptContext ctx)
throws ScriptException {
return eval(str, ctx, null);
}
public Object eval(Reader reader, ScriptContext ctx)
throws ScriptException {
return eval(readFully(reader), ctx);
}
public Object eval(String script, ScriptContext context, DiagnosticListener<JavaFileObject> listener) throws ScriptException {
VisageScriptCompiledScript cscript = parse(script, context, listener);
return cscript.eval(context);
}
public Object eval(Reader reader, ScriptContext context, DiagnosticListener<JavaFileObject> listener) throws ScriptException {
return eval(readFully(reader), context, listener);
}
public Object eval(String script, Bindings bindings, DiagnosticListener<JavaFileObject> listener) throws ScriptException {
ScriptContext ctx = getContext();
ctx.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
return eval(script, ctx, listener);
}
public Object eval(Reader reader, Bindings bindings, DiagnosticListener<JavaFileObject> listener) throws ScriptException {
ScriptContext ctx = getContext();
ctx.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
return eval(reader, ctx, listener);
}
public ScriptEngineFactory getFactory() {
if (factory == null) {
factory = new VisageScriptEngineFactory();
}
return factory;
}
public Bindings createBindings() {
return new SimpleBindings();
}
void setFactory(ScriptEngineFactory factory) {
this.factory = factory;
}
// Internals only below this point
int counter;
private VisageScriptCompiledScript parse(String str, ScriptContext ctx,
final DiagnosticListener<JavaFileObject> listener) throws ScriptException {
String fileName = getFileName(ctx);
if ("<STDIN>".equals(fileName))
fileName = "stdin" + ++counter;
String sourcePath = getSourcePath(ctx);
String classPath = getClassPath(ctx);
String script = str;
VisageScriptContext scontext = getVisageScriptContext(ctx);
boolean copyVars = true;
// JSR-223 requirement - but unsure if it's a good idea.
// ctx.setAttribute("context", ctx, ScriptContext.ENGINE_SCOPE);
if (copyVars) {
Bindings globals = ctx.getBindings(ScriptContext.GLOBAL_SCOPE);
if (globals != null) {
for (Map.Entry<String, Object> entry : globals.entrySet()) {
String key = entry.getKey();
if (key.indexOf('.') >= 0)
continue; // Kludge FIXME
Symbol sym = scontext.compiler.names == null ? null : scontext.compiler.lookup(key);
if (sym == null) {
scontext.compiler.compile(fileName+"_"+key, "public var <<"+key+">>;",
ctx.getErrorWriter(), null, classPath, listener);
}
}
}
for (Map.Entry<String, Object> entry : ctx.getBindings(ScriptContext.ENGINE_SCOPE).entrySet()) {
String key = entry.getKey();
if (key.indexOf('.') >= 0)
continue; // Kludge FIXME
Symbol sym = scontext.compiler.names == null ? null : scontext.compiler.lookup(key);
if (sym == null) {
scontext.compiler.compile(fileName+"_"+key, "public var <<"+key+">>;",
ctx.getErrorWriter(), null, classPath, listener);
}
}
}
VisageCompiledScript compiled = scontext.compiler.compile(fileName, script,
ctx.getErrorWriter(), sourcePath, classPath, listener);
if (compiled == null) {
throw new ScriptException("compilation failed");
}
return new VisageScriptCompiledScript(compiled);
}
private static String getFileName(ScriptContext ctx) {
int scope = ctx.getAttributesScope(ScriptEngine.FILENAME);
if (scope != -1) {
Object fn = ctx.getAttribute(ScriptEngine.FILENAME, scope);
return fn.toString();
} else {
return "___VISAGE_SCRIPT___.visage";
}
}
// for certain variables, we look for System properties. This is
// the prefix used for such System properties
private static final String SYSPROP_PREFIX = "org.visage.tools.script.";
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();
} else {
// look for "org.visage.tools.script.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();
} else {
// look for "org.visage.tools.script.classpath"
String res = System.getProperty(SYSPROP_PREFIX + CLASSPATH);
if (res == null) {
res = System.getProperty("java.class.path");
}
return res;
}
}
// read a Reader fully and return the content as string
private String readFully(Reader reader) throws ScriptException {
try {
return VisageScriptCompiler.readFully(reader);
} catch (IOException exp) {
throw new ScriptException(exp);
}
}
public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException {
if (thiz == null)
throw new ScriptException("target object not specified");
if (name == null)
throw new ScriptException("method name not specified");
Method method = VisageScriptContext.findMethod(thiz.getClass(), name, args);
if (method == null)
throw new ScriptException(new NoSuchMethodException());
try {
method.setAccessible(true);
return method.invoke(thiz, args);
} catch (Exception e) {
throw new ScriptException(e);
}
}
public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
if (name == null)
throw new ScriptException("method name not specified");
VisageScriptContext scontext = getVisageScriptContext(getContext());
Name nname = scontext.compiler.names.fromString(name);
for (Scope.Entry e = scontext.compiler.namedImportScope.lookup(nname);
e.sym != null; e = e.next()) {
// FIXME - should also handle VarSymbol whose type is a FunctionType.
if (e.sym instanceof Symbol.MethodSymbol) {
Class script = scontext.loadSymbolClass(e.sym);
Method method = VisageScriptContext.findMethod(script, name, args);
if (method != null) {
try {
Constructor cons = findDefaultConstructor(script);
cons.setAccessible(true);
Object instance = cons.newInstance();
method.setAccessible(true);
return method.invoke(instance, args);
} catch (Exception ex) {
throw new ScriptException(ex);
}
}
}
}
throw new ScriptException(new NoSuchMethodException(name));
}
private Constructor findDefaultConstructor(Class script) throws NoSuchMethodException {
Constructor[] cs = script.getDeclaredConstructors();
for (Constructor c : cs) {
if (c.getParameterTypes().length == 0) {
return c;
}
}
throw new NoSuchMethodException("default constructor");
}
public <T> T getInterface(Class<T> clazz) {
return makeInterface(null, clazz);
}
public <T> T getInterface(Object thiz, Class<T> clazz) {
if (thiz == null) {
throw new IllegalArgumentException("script object is null");
}
return makeInterface(thiz, clazz);
}
private <T> T makeInterface(Object obj, Class<T> clazz) {
final Object thiz = obj;
if (clazz == null || !clazz.isInterface()) {
throw new IllegalArgumentException("interface Class expected");
}
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[] { clazz },
new InvocationHandler() {
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
if (thiz == null)
return invokeFunction(m.getName(), args);
return invokeMethod(thiz, m.getName(), args);
}
});
}
// Workarounds for backward compatibility with old JSR-223 api on mac os
public Object invoke(String name, Object...args) throws ScriptException, NoSuchMethodException
{
return invokeFunction(name, args);
}
public Object invoke(Object thiz, String name, Object...args) throws ScriptException, NoSuchMethodException {
return invokeMethod(thiz, name, args);
}
}