blob: c9da04d67ce07bb739032c78c947abbfabfd2d14 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}