/******************************************************************************* * Copyright (c) 2006-2013 The RCP Company and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * The RCP Company - initial API and implementation *******************************************************************************/ package com.rcpcompany.uibindings.scriptengines.javascript.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.EcmaError; import org.mozilla.javascript.ImporterTopLevel; import org.mozilla.javascript.RhinoException; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Undefined; import com.rcpcompany.uibindings.scripting.AbstractScriptEngine; import com.rcpcompany.uibindings.scripting.IScriptDependency; import com.rcpcompany.uibindings.scripting.IScriptEngine; import com.rcpcompany.uibindings.scripting.IScriptEngineFactory; import com.rcpcompany.uibindings.scripting.IScriptEnginePackage; import com.rcpcompany.uibindings.scripting.IScriptEvaluationContext; import com.rcpcompany.uibindings.scripting.IScriptExpression; import com.rcpcompany.utils.logging.LogUtils; /** * JavaScript based {@link IScriptEngine}. * * @author Tonny Madsen, The RCP Company */ public class JSScriptEngine extends AbstractScriptEngine implements IScriptEngine { /** * The context used for our engine. */ private final Context myContext; /** * The global scope where all variables are installed. */ private final Scriptable myGlobalScope; /** * The current script expression. */ private IScriptExpression myCurrentExpression = null; /** * Returns the current script expression. * * @return the expression */ public IScriptExpression getCurrentExpression() { return myCurrentExpression; } /** * Sets the current script expression * * @param expression the new expression */ public void setCurrentExpression(IScriptExpression expression) { myCurrentExpression = expression; } /** * Constructs and returns a new JavaScript engine. */ public JSScriptEngine() { myContext = ContextFactory.getGlobal().enterContext(); myContext.setErrorReporter(new MyErrorReporter()); myContext.setWrapFactory(new EMFWrapFactory()); myGlobalScope = new MyTopLevel(myContext); } @Override public void dispose() { } /** * The script dependencies accumulated by {@link #addDependency(EObject, EStructuralFeature)} and friends. */ private static final List<IScriptDependency> myDependencies = new ArrayList<IScriptDependency>(); @Override public void evaluate(IScriptExpression expression) { Object value = null; String errorMessage = null; try { setCurrentExpression(expression); myDependencies.clear(); final String script = expression.getScript(); if (script == null) { return; } final Context cx = Context.getCurrentContext(); LogUtils.debug(cx, "cx=" + cx + ": " + script); value = cx.evaluateString(myGlobalScope, script, "script", 1, null); } catch (final EcmaError ex) { errorMessage = ex.getErrorMessage(); } catch (final RhinoException ex) { LogUtils.error(this, ex); errorMessage = ex.getLocalizedMessage(); } finally { if (value instanceof Scriptable) { if (expression.getExpectedValueClass() == String.class) { value = ((Scriptable) value).getDefaultValue(String.class); } else if (Number.class.isAssignableFrom(expression.getExpectedValueClass())) { value = ((Scriptable) value).getDefaultValue(Number.class); } else if (expression.getExpectedValueClass() == Boolean.class) { value = ((Scriptable) value).getDefaultValue(Boolean.class); } else { value = ((Scriptable) value).getDefaultValue(String.class); } } else if (value == Undefined.instance) { value = null; } else if (value == Scriptable.NOT_FOUND) { value = null; } expression.setCurrentValue(value); expression.setErrorMessage(errorMessage); /* * Update the dependencies... */ expression.updateDependencies(myDependencies); setCurrentExpression(null); } } public static void addDependency(EObject obj, EStructuralFeature sf) { LogUtils.debug(obj, obj + " - " + sf.getName()); final IScriptDependency dependency = IScriptEngineFactory.eINSTANCE.createScriptDependency(); dependency.setObject(obj); dependency.setFeature(sf); myDependencies.add(dependency); } public static void addDependency(EObject obj, EStructuralFeature sf, int index) { LogUtils.debug(obj, obj + " - " + sf.getName() + "[" + index + "]"); final IScriptDependency dependency = IScriptEngineFactory.eINSTANCE.createScriptDependency(); dependency.setObject(obj); dependency.setFeature(sf); dependency.setIndex(index); myDependencies.add(dependency); } public static void addDependency(EObject obj, EStructuralFeature sf, Object key) { LogUtils.debug(obj, obj + " - " + sf.getName() + "[" + key + "]"); final IScriptDependency dependency = IScriptEngineFactory.eINSTANCE.createScriptDependency(); dependency.setObject(obj); dependency.setFeature(sf); dependency.setKey(key); myDependencies.add(dependency); } /** * An {@link ImporterTopLevel} scope that "knows" about {@link IScriptEvaluationContext} */ public class MyTopLevel extends ImporterTopLevel { /** * Constructs and returns a new top-level scope. * * @param cx the context */ public MyTopLevel(Context cx) { super(cx); } @Override public Object get(String name, Scriptable start) { /* * Look for the name first between the current evaluation context variables - and parents */ final IScriptExpression expression = getCurrentExpression(); if (expression != null) { IScriptEvaluationContext context = expression.getEvaluationContext(); while (context != null) { if (context.getVariables().containsKey(name)) { addDependency(context, IScriptEnginePackage.Literals.SCRIPT_EVALUATION_CONTEXT__VARIABLES, name); final Object o = context.getVariables().get(name); return Context.javaToJS(o, this); } context = context.getParent(); } } /* * Fall back on the "normal" global variables */ return super.get(name, start); } @Override public Object[] getIds() { final Set<Object> contextIDs = new HashSet<Object>(Arrays.asList(super.getIds())); final IScriptExpression expression = getCurrentExpression(); if (expression != null) { IScriptEvaluationContext context = expression.getEvaluationContext(); while (context != null) { contextIDs.addAll(context.getVariables().keySet()); context = context.getParent(); } } return contextIDs.toArray(); } } }