| /******************************************************************************* |
| * Copyright (c) 2009 IBM Corporation 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: IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.e4.languages.javascript.debug.rhino; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.e4.languages.javascript.debug.rhino.ScriptImpl.ScriptRecord; |
| import org.mozilla.javascript.BaseFunction; |
| import org.mozilla.javascript.Context; |
| import org.mozilla.javascript.ScriptRuntime; |
| import org.mozilla.javascript.Scriptable; |
| import org.mozilla.javascript.ScriptableObject; |
| import org.mozilla.javascript.Undefined; |
| import org.mozilla.javascript.debug.DebugFrame; |
| import org.mozilla.javascript.debug.DebuggableScript; |
| import org.mozilla.javascript.debug.Debugger; |
| |
| public class DebugFrameImpl implements DebugFrame { |
| |
| private final Long id; |
| private final Context context; |
| private final ContextData contextData; |
| private final DebuggableScript debuggableScript; |
| private final ScriptImpl script; |
| private final ScriptRecord record; |
| |
| private final HashMap handles = new HashMap(); |
| private final IdentityHashMap handledObjects = new IdentityHashMap(); |
| private Scriptable activation; |
| private Scriptable thisObj; |
| private int lineNumber; |
| |
| public DebugFrameImpl(Long frameId, Context context, DebuggableScript debuggableScript, ScriptImpl script) { |
| this.id = frameId; |
| this.context = context; |
| this.contextData = (ContextData) context.getDebuggerContextData(); |
| this.debuggableScript = debuggableScript; |
| this.script = script; |
| this.record = script.getRecord(debuggableScript); |
| this.lineNumber = record.firstLine; |
| } |
| |
| public Long getId() { |
| return id; |
| } |
| |
| public ScriptImpl getScript() { |
| return script; |
| } |
| |
| public Integer getLineNumber() { |
| return new Integer(lineNumber); |
| } |
| |
| public void onDebuggerStatement(Context cx) { |
| contextData.debuggerStatement(script, new Integer(lineNumber)); |
| } |
| |
| public void onEnter(Context cx, Scriptable activation, Scriptable thisObj, Object[] args) { |
| this.activation = activation; |
| this.thisObj = thisObj; |
| initializeHandles(); |
| contextData.pushFrame(this, script, new Integer(lineNumber), debuggableScript.getFunctionName()); |
| } |
| |
| public void onExit(Context cx, boolean byThrow, Object resultOrException) { |
| this.activation = null; |
| this.thisObj = null; |
| clearHandles(); |
| contextData.popFrame(byThrow, resultOrException); |
| } |
| |
| public void onExceptionThrown(Context cx, Throwable ex) { |
| contextData.exceptionThrown(ex); |
| } |
| |
| public void onLineChange(Context cx, int lineNumber) { |
| initializeHandles(); |
| this.lineNumber = record.firstLine + lineNumber; |
| contextData.lineChange(script, new Integer(this.lineNumber)); |
| } |
| |
| public Object evaluate(String source) { |
| Context evalContext = context.getFactory().enter(); |
| Debugger debugger = evalContext.getDebugger(); |
| Object debuggerContextData = evalContext.getDebuggerContextData(); |
| evalContext.setDebugger(null, null); |
| try { |
| Object result = ScriptRuntime.evalSpecial(evalContext, activation, thisObj, new Object[] { source }, "eval", 0); |
| Long handle = createHandle(result); |
| return serialize(handle, result); |
| } finally { |
| evalContext.setDebugger(debugger, debuggerContextData); |
| Context.exit(); |
| } |
| } |
| |
| public boolean evaluateCondition(String condition) { |
| Debugger debugger = context.getDebugger(); |
| context.setDebugger(null, null); |
| try { |
| Object result = ScriptRuntime.evalSpecial(context, activation, thisObj, new Object[] { condition }, "condition", 0); |
| return ScriptRuntime.toBoolean(result); |
| } finally { |
| context.setDebugger(debugger, contextData); |
| } |
| } |
| |
| public Object lookup(Long handle) { |
| |
| Object result = handles.get(handle); |
| context.getFactory().enter(); |
| try { |
| return serialize(handle, result); |
| } finally { |
| Context.exit(); |
| } |
| } |
| |
| public Object toJSON() { |
| Map result = new HashMap(); |
| result.put("threadId", contextData.getThreadId()); |
| result.put("contextId", contextData.getId()); |
| result.put("frameId", id); |
| result.put("scriptId", script.getId()); |
| result.put("line", new Integer(lineNumber)); |
| if (debuggableScript.isFunction()) |
| result.put("function", debuggableScript.getFunctionName()); |
| result.put("ref", new Integer(0)); |
| |
| return result; |
| } |
| |
| public Object serialize(Long handle, Object object) { |
| Map result = new HashMap(); |
| result.put("handle", handle); |
| |
| // "undefined", "null", "boolean", "number", "string", "object", "function" or "frame" |
| if (object == Undefined.instance) { |
| serializeUndefined(result); |
| } else if (object == null) { |
| serializeNull(result); |
| } else if (object instanceof Boolean) { |
| serializeSimpleType(object, "boolean", result); |
| } else if (object instanceof Number) { |
| serializeSimpleType(object, "number", result); |
| } else if (object instanceof String) { |
| serializeSimpleType(object, "string", result); |
| } else if (object instanceof Scriptable) { |
| Scriptable scriptable = (Scriptable) object; |
| serializeFunctionOrObject(scriptable, result); |
| } else if (object == this) |
| serializeFrame(result); |
| return result; |
| } |
| |
| private void serializeUndefined(Map result) { |
| result.put("type", "undefined"); |
| } |
| |
| private void serializeNull(Map result) { |
| result.put("type", "null"); |
| } |
| |
| private void serializeSimpleType(Object object, String type, Map result) { |
| result.put("type", type); |
| result.put("value", object); |
| } |
| |
| private void serializeFunctionOrObject(Scriptable scriptable, Map result) { |
| boolean isFunction = scriptable instanceof BaseFunction; |
| result.put("type", isFunction ? "function" : "object"); |
| result.put("className", scriptable.getClassName()); |
| |
| Object constructorFunction = ScriptableObject.getProperty(scriptable, (String) "constructor"); |
| result.put("constructorFunction", createRef(constructorFunction)); |
| |
| Scriptable protoObject = findProtoObject(scriptable); |
| result.put("protoObject", createRef(protoObject)); |
| |
| result.put("prototypeObject", createRef(scriptable.getPrototype())); |
| |
| if (isFunction) { |
| String functionName = ((BaseFunction) scriptable).getFunctionName(); |
| result.put("name", functionName); |
| } |
| |
| result.put("properties", createProperties(scriptable)); |
| } |
| |
| private void serializeFrame(Map result) { |
| result.put("type", "frame"); |
| List properties = createProperties(activation); |
| properties.add(createProperty("this", thisObj)); |
| result.put("properties", properties); |
| } |
| |
| private Scriptable findProtoObject(Scriptable scriptable) { |
| // TODO: cache this more sensibly |
| Scriptable protoObject = scriptable.getPrototype(); |
| while (true) { |
| Scriptable parent = protoObject.getPrototype(); |
| if (parent == null || parent.equals(protoObject)) |
| break; |
| protoObject = parent; |
| } |
| return protoObject; |
| } |
| |
| private List createProperties(Scriptable scriptable) { |
| ArrayList properties = new ArrayList(); |
| Object[] ids = scriptable.getIds(); |
| for (int i = 0; i < ids.length; i++) { |
| Object id = ids[i]; |
| Object value = null; |
| if (id instanceof String) { |
| value = ScriptableObject.getProperty(scriptable, (String) id); |
| } else if (id instanceof Number) { |
| value = ScriptableObject.getProperty(scriptable, ((Number) id).intValue()); |
| } else |
| continue; |
| |
| Map property = createProperty(id, value); |
| properties.add(property); |
| } |
| return properties; |
| } |
| |
| private Map createProperty(Object id, Object value) { |
| Map property = createRef(value); |
| property.put("name", id); |
| return property; |
| } |
| |
| private Map createRef(Object object) { |
| Map map = new HashMap(2); |
| map.put("ref", createHandle(object)); |
| return map; |
| } |
| |
| private void clearHandles() { |
| handles.clear(); |
| handledObjects.clear(); |
| } |
| |
| private void initializeHandles() { |
| if (handles.size() != 1) { |
| clearHandles(); |
| createHandle(this); |
| } |
| } |
| |
| private Long createHandle(Object object) { |
| Long handle = (Long) handledObjects.get(object); |
| if (handle == null) { |
| handle = new Long(nextHandle()); |
| handles.put(handle, object); |
| handledObjects.put(object, handle); |
| } |
| return handle; |
| } |
| |
| private int nextHandle() { |
| return handles.size(); |
| } |
| |
| public Object getThreadId() { |
| return contextData.getThreadId(); |
| } |
| } |