package com.drawbridge.jsengine; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import com.drawbridge.dm.DMImageFactory.DMImage; import com.drawbridge.dm.DMPanel; import com.drawbridge.jsengine.ast.DBParser; import com.drawbridge.jsengine.ast.ParserListener; import com.drawbridge.jsengine.jsobjects.Colour; import com.drawbridge.jsengine.jsobjects.JSImage; import com.drawbridge.jsengine.jsobjects.JSNativeFunction; import com.drawbridge.jsengine.jsobjects.JSNumber; import com.drawbridge.text.TextPanel; import com.drawbridge.utils.Utils; import com.drawbridge.vl.VLPanel; import com.google.caja.ancillary.linter.LintHelper; public class JsEngine { /** * List of listeners that need to be notified when the model is updated */ private List<JSEngineListener> mListeners; private final Scope mGlobalScope; private static JsEngine mInstance = null; private final String mScaffoldCode; public final Integer mScaffoldLines; private int mContinueASTExecutionRequests; private final Runnable mExecutionRunnable; private Thread mExecutionThread = null; public static JsEngine getInstance() { if (mInstance != null) { return mInstance; } else { mInstance = new JsEngine(); return mInstance; } } public JsEngine() { mListeners = new ArrayList<JSEngineListener>(); mGlobalScope = new Scope(this, null); mScaffoldCode = Utils.loadTextFromPackagedFile("/SimpleTweeningJS/stubs.js"); mScaffoldLines = Utils.getNumberOfLinesFromPackagedFile("/SimpleTweeningJS/stubs.js"); mExecutionRunnable = new Runnable() { @Override public void run() { while (mContinueASTExecutionRequests > 0) { mContinueASTExecutionRequests = 0; getGlobalScope().resetVariables(); // Load any existing DMImages into JS Memory loadDMImagesIntoJSEngine(); try { DBParser.getInstance(); DBParser.getInstance().evaluate(); } catch (Exception e) { e.printStackTrace(); } try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } } }; } public synchronized Scope getGlobalScope() { return mGlobalScope; } public synchronized void addModelListener(JSEngineListener listener) { mListeners.add(listener); } /** * Updates listeners that a scope has changed has changed * * @param child * - the scope the change affects new JSImage( * @param source * - where the change was made */ private synchronized void notifyModelListeners(Scope child, JSEngineListener source) { synchronized (mListeners) { for (JSEngineListener name : mListeners) { if (!name.equals(source)) { name.modelChange(child); } } } } public void childChanged(Scope scope, JSEngineListener source) { notifyModelListeners(scope, source); } public interface JSEngineListener { /** * Whenever the main model changes, each subscriber gets notified * * @param scope * @param source */ public void modelChange(Scope scope); } /** * Executes code in the JSEngine. If evaluate is false, the code is simply parsed and pushed to listeners * * @param sourceListener * @param code * @param evaluate */ public void executeCode(ParserListener sourceListener, String code, boolean evaluate) { Utils.out.println(getClass(), "Execute Code from " + ((sourceListener != null) ? sourceListener.getClass().getSimpleName() : "Utils") + (evaluate? "evaluating" : " not evaluating")); LinkedList<ParserListener> listeners = new LinkedList<ParserListener>(); if (TextPanel.hasInstance()) listeners.add(TextPanel.getInstance()); if (VLPanel.hasInstance()) listeners.add(VLPanel.getInstance().mCanvas.getModel()); DBParser.getInstance().setParams(sourceListener, listeners, mGlobalScope, code); long before = System.currentTimeMillis(); DBParser.getInstance().parse(); if (evaluate) { getGlobalScope().resetVariables(); // Load any existing DMImages into JS Memory loadDMImagesIntoJSEngine(); try { DBParser.getInstance().evaluate(); } catch (Exception e) { e.printStackTrace(); } JsEngine.getInstance().getGlobalScope().printScopeDeep(0); } LintHelper lh = new LintHelper(); String fullCode = mScaffoldCode + code; lh.runTest(mScaffoldLines, fullCode); Utils.out.println(fullCode); long after = System.currentTimeMillis();//INVALID_ASSIGNMENT long difference = after - before; Utils.out.println(getClass(), "Time taken to executeCode: " + difference + " milliseconds"); } /** * Adds the code to an execution queue, which is executed according to how fast executions take. It then takes the * next to be processed * * @param sourceListener * @param code */ public void requestExecutionOfModifiedAST() { mContinueASTExecutionRequests++; // Utils.out.println("Requested:" + mNumberRequested + ", processed:" + // mNumberProcessed); // If thread isn't working, start it if (mExecutionThread != null && mExecutionThread.isAlive()) { // it will process if it has time } else { mExecutionThread = new Thread(mExecutionRunnable); mExecutionThread.start(); } } // Push info from model into JSEngine public void loadDMImagesIntoJSEngine() { LinkedList<DMImage> images = DMPanel.getInstance().mModel.getModelObjects(); for (int i = 0; i < images.size(); i++) { JSImage newImage = new JSImage(images.get(i)); String name = "image" + i; SymbolTableEntry entry = new SymbolTableEntry(newImage); JsEngine.getInstance().getGlobalScope().declareVariable(null, name, entry); } JsEngine.getInstance().getGlobalScope().declareVariable(null, "WIDTH", new SymbolTableEntry(new JSNumber(DMPanel.getInstance().getWidth()))); JsEngine.getInstance().getGlobalScope().declareVariable(null, "HEIGHT", new SymbolTableEntry(new JSNumber(DMPanel.getInstance().getHeight()))); JsEngine.getInstance().getGlobalScope().declareVariable(null, "loadImage", new SymbolTableEntry(new JSNativeFunction(null, "loadImage", new Class<?>[] { JSNumber.class, JSNumber.class, JSNumber.class, JSNumber.class, JSNumber.class }))); JsEngine.getInstance().getGlobalScope().declareVariable(null, "setTimeToDestination", new SymbolTableEntry(new JSNativeFunction(null, "setTimeToDestination", new Class<?>[] { JSNumber.class }))); JsEngine.getInstance().getGlobalScope().declareVariable(null, "Colour", new SymbolTableEntry(new Colour())); } }