/*
* Scriptographer
*
* This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator
* http://scriptographer.org/
*
* Copyright (c) 2002-2010, Juerg Lehni
* http://scratchdisk.com/
*
* All rights reserved. See LICENSE file for details.
*
* File created on Feb 19, 2007.
*/
package com.scratchdisk.script.rhino;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Map;
import org.eclipse.wst.jsdt.debug.rhino.debugger.RhinoDebugger;
import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.PropertyDescriptor;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.tools.debugger.ScopeProvider;
import com.scratchdisk.script.ArgumentReader;
import com.scratchdisk.script.PropertyObserver;
import com.scratchdisk.script.Scope;
import com.scratchdisk.script.Script;
import com.scratchdisk.script.ScriptEngine;
/**
* @author lehni
*
*/
public class RhinoEngine extends ScriptEngine implements ScopeProvider {
protected TopLevel topLevel;
protected Context context;
protected RhinoWrapFactory wrapFactory;
private RhinoScope globalScope;
private RhinoDebugger debugger;
public RhinoEngine(RhinoWrapFactory wrapFactory) {
super("JavaScript", "js");
this.wrapFactory = wrapFactory;
// Set the engine pointer by hand here, as otherwise
// RhinoWrapFactory would take an argument, and could not be created
// in the super constructor call, which would be annoying...
// TODO: consider a cleaner solution
wrapFactory.engine = this;
// Produce a ContextFactory that only redirects calls to RhinoEngine,
// so they can be overridden easily in inherited classes.
ContextFactory contextFactory = new ContextFactory() {
protected boolean hasFeature(Context cx, int feature) {
return RhinoEngine.this.hasFeature(cx,
feature, super.hasFeature(cx, feature));
}
protected Context makeContext() {
Context context = super.makeContext();
RhinoEngine.this.enter(context);
return context;
}
protected void observeInstructionCount(Context cx,
int instructionCount) {
RhinoEngine.this.observeInstructionCount(cx, instructionCount);
}
};
ContextFactory.initGlobal(contextFactory);
// The debugger needs to be created before the context, otherwise
// notification won't work
String rhinoDebug = System.getProperty("rhino.debug");
if (rhinoDebug != null) {
try {
debugger = new RhinoDebugger(rhinoDebug);
debugger.start();
contextFactory.addListener(debugger);
} catch (Exception e) {
e.printStackTrace();
}
}
context = contextFactory.enterContext();
topLevel = this.makeTopLevel(context);
}
public RhinoEngine() {
this(new RhinoWrapFactory());
}
protected TopLevel makeTopLevel(Context context) {
return new TopLevel(context);
}
/**
* @param cx
* @param instructionCount
*/
protected void observeInstructionCount(Context cx, int instructionCount) {
}
protected boolean hasFeature(Context cx, int feature, boolean defaultValue) {
switch (feature) {
case Context.FEATURE_E4X:
return cx.getE4xImplementationFactory() != null;
}
return defaultValue;
}
protected void enter(Context context) {
context.setApplicationClassLoader(getClass().getClassLoader());
context.setWrapFactory(wrapFactory);
}
protected Script compileScript(File file)
throws RhinoScriptException, IOException {
FileReader in = null;
try {
in = new FileReader(file);
return new RhinoScript(this, context.compileReader(
in, file.getPath(), 1, null), file);
} catch (RhinoException e) {
throw new RhinoScriptException(this, e);
} finally {
if (in != null)
in.close();
}
}
public Script compile(String code, String name) {
return new RhinoScript(this,
context.compileString(code, name, 1, null), null);
}
public Object evaluate(String code, String name, Scope scope)
throws RhinoScriptException {
try {
// TODO: Typecast to RhinoScope can be wrong, e.g. when calling
// from another language
return context.evaluateString(((RhinoScope) scope).getScope(), code,
name, 1, null);
} catch (RhinoException e) {
throw new RhinoScriptException(this, e);
}
}
public Scope createScope() {
Scriptable scope = new NativeObject();
// Sharing the top level scope:
// https://developer.mozilla.org/En/Rhino_documentation/Scopes_and_Contexts
scope.setPrototype(topLevel);
scope.setParentScope(null);
return new RhinoScope(this, scope);
}
public Scope getScope(Object object) {
if (object instanceof Scriptable)
return new RhinoScope(this, (Scriptable) object);
return null;
}
public Scope getGlobalScope() {
if (globalScope == null)
globalScope = new RhinoScope(this, topLevel);
return globalScope;
}
public Scriptable getScope() {
return topLevel;
}
/**
* Required by RhinoCallable, to find the wrapper object (scope) for the
* native object the callalbe is executed on.
*/
protected static Scriptable getWrapper(Object object, Scriptable scope) {
if (object instanceof Scriptable) {
return (Scriptable) object;
} else {
Context cx = Context.getCurrentContext();
return cx.getWrapFactory().wrapAsJavaObject(cx, scope,
object, object.getClass(), false);
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T toJava(Object object, Class<T> type) {
return (T) Context.jsToJava(object, type);
}
@Override
public ArgumentReader getArgumentReader(Object object) {
return wrapFactory.getArgumentReader(object);
}
@Override
public boolean observe(Map object, Object key, PropertyObserver observer) {
if (object instanceof ScriptableObject) {
ScriptableObject obj = (ScriptableObject) object;
Context cx = Context.getCurrentContext();
PropertyDescriptor desc = obj.getOwnPropertyDescriptor(cx, key);
if (desc != null && desc.isDataDescriptor()) {
ObserverGetterSetter getterSetter =
new ObserverGetterSetter(obj, key, desc, observer);
obj.defineOwnProperty(cx, key, new PropertyDescriptor(
getterSetter,
getterSetter,
desc.isEnumerable(),
desc.isConfigurable(),
true));
}
return true;
}
return false;
}
/*
* Use one function as both getter and setter and look at the argument count
* to decide which one of the two is required.
*/
private static class ObserverGetterSetter extends BaseFunction {
private ScriptableObject object;
private Object id;
private PropertyDescriptor descriptor;
private PropertyObserver observer;
ObserverGetterSetter(ScriptableObject object, Object id,
PropertyDescriptor descriptor, PropertyObserver observer) {
this.object = object;
this.id = id;
this.descriptor = descriptor;
this.observer = observer;
}
public Object call(Context cx, Scriptable scope, Scriptable thisObj,
Object[] args) {
if (args.length == 1) {
Object value = args[0];
descriptor.setValue(value);
observer.onChangeProperty(object, id, value);
return Undefined.instance;
} else {
return descriptor.getValue();
}
}
}
}