/*
* 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 Apr 10, 2007.
*/
package com.scriptographer.script.rhino;
import java.io.File;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.OperatorHandler;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Token;
import com.scratchdisk.script.Scope;
import com.scratchdisk.script.ScriptCanceledException;
import com.scratchdisk.script.rhino.RhinoScope;
import com.scriptographer.ScriptographerEngine;
/**
* @author lehni
*
*/
public class RhinoEngine extends com.scratchdisk.script.rhino.RhinoEngine implements OperatorHandler {
public RhinoEngine() {
super(new RhinoWrapFactory());
}
protected com.scratchdisk.script.rhino.TopLevel makeTopLevel(Context context) {
return new TopLevel(context);
}
protected boolean hasFeature(Context cx, int feature, boolean defaultValue) {
switch (feature) {
case Context.FEATURE_DYNAMIC_SCOPE:
return true;
}
return super.hasFeature(cx, feature, defaultValue);
}
protected void enter(Context context) {
super.enter(context);
// Use pure interpreter mode to allow for
// observeInstructionCount(Context, int) to work
context.setOptimizationLevel(-1);
// Make Rhino runtime to call observeInstructionCount
// each 20000 bytecode instructions
context.setInstructionObserverThreshold(20000);
context.setOperatorHandler(this);
}
protected void observeInstructionCount(Context cx, int instructionCount) {
if (!ScriptographerEngine.updateProgress())
throw new ScriptCanceledException();
}
public String[] getScriptPath(File file) {
return ScriptographerEngine.getScriptPath(file, true);
}
public Object handleOperator(Context cx, Scriptable scope, int operator, Object lhs, Object rhs) {
// There is a very simple convention for arithmetic operations on objects:
// Just try to get the according functions on scriptable objects,
// and perform the operation by executing these.
// Fall back on the default ScriptRuntime.add for adding,
// return null for everything else.
// Wrap String as Scriptable for some of the operators, so we can access
// its prototype easily too.
// Note that only the operators that are natively defined for JS can
// be overridden here!
if (lhs instanceof String && (
operator == Token.SUB ||
operator == Token.MUL ||
operator == Token.DIV))
lhs = ScriptRuntime.toObject(cx, scope, lhs);
// Now perform the magic
if (lhs instanceof Scriptable) {
String name = null;
switch (operator) {
case Token.ADD:
name = "add";
break;
case Token.SUB:
name = "subtract";
break;
case Token.MUL:
name = "multiply";
break;
case Token.DIV:
name = "divide";
break;
case Token.MOD:
name = "modulo";
break;
case Token.EQ:
case Token.NE:
name = "equals";
break;
}
if (name != null) {
Scriptable scriptable = (Scriptable) lhs;
Object obj = ScriptableObject.getProperty(scriptable, name);
if (obj instanceof Callable) {
Object result = ((Callable) obj).call(cx, scope, scriptable, new Object[] { rhs });
if (operator == Token.EQ || operator == Token.NE) {
boolean value = ScriptRuntime.toBoolean(result);
if (operator == Token.NE)
value = !value;
return ScriptRuntime.wrapBoolean(value);
} else {
return result;
}
}
}
}
return null;
}
public Object handleSignOperator(Context cx, Scriptable scope, int operator, Object rhs) {
switch (operator) {
case Token.NEG:
// Wrap String as Scriptable, so we can access its prototype easily too.
if (rhs instanceof String)
rhs = ScriptRuntime.toObject(cx, scope, rhs);
// Now perform the magic
if (rhs instanceof Scriptable) {
Scriptable scriptable = (Scriptable) rhs;
Object obj = ScriptableObject.getProperty(scriptable, "negate");
if (obj instanceof Callable)
return ((Callable) obj).call(cx, scope, scriptable, new Object[] {});
}
break;
case Token.POS:
// Simple return the rhs, since the standard JS way would be to force conversion
// to a number, which would give NaN for new Point(1, 2) * 1, since this is
// converted top + new Point(1, 2) by the interpreter...
return rhs;
}
return null;
}
public Scope createScope() {
Scope scope = super.createScope();
// Override global to point to the 'local' global scope ;)
scope.put("global", ((RhinoScope) scope).getScope());
return scope;
}
}