package org.eclipse.dltk.rhino.dbgp; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Observable; import java.util.Observer; import java.util.WeakHashMap; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeJavaArray; import org.mozilla.javascript.NativeJavaClass; import org.mozilla.javascript.NativeJavaObject; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Undefined; import org.mozilla.javascript.UniqueTag; import org.mozilla.javascript.Wrapper; import org.mozilla.javascript.debug.DebugFrame; import org.mozilla.javascript.debug.DebuggableScript; import org.mozilla.javascript.debug.Debugger; import org.mozilla.javascript.debug.IDeguggerWithWatchPoints; import org.mozilla.javascript.xml.XMLObject; public class DBGPDebugger extends Thread implements Debugger, Observer, IDeguggerWithWatchPoints { private Socket socket; private OutputStream out; private HashMap strategies = new HashMap(); HashMap properties = new HashMap(); String runTransctionId; static abstract class Command { abstract void parseAndExecute(String command, HashMap options); } private static void writeResponseLength(OutputStream out, int value) throws IOException { out.write(String.valueOf(value).getBytes()); } void printResponse(String response) { try { byte[] bytes = response.getBytes("UTF-8"); //$NON-NLS-1$ writeResponseLength(out, bytes.length); out.write(0); out.write(bytes, 0, bytes.length); out.write(0); out.flush(); } catch (IOException e) { try { socket.close(); } catch (IOException e2) { // ignore } } } DBGPStackManager stackmanager; public boolean isInited; public void setContext(Context cx) { DBGPStackManager manager = DBGPStackManager.getManager(cx); manager.setDebugger(this); this.stackmanager = manager; } public DBGPDebugger(Socket socket, String file, String string, Context ct) throws IOException { super(); this.socket = socket; stackmanager = DBGPStackManager.getManager(ct); stackmanager.suspend(); out = new BufferedOutputStream(socket.getOutputStream(), 2048); stackmanager.setDebugger(this); String response = "<init appid=\"APPID\"\r\n" + " idekey=\"" + string + "\"\r\n" + " session=\"" + string + "\"\r\n" + " thread=\"THREAD_ID\"\r\n" + " parent=\"PARENT_APPID\"\r\n" + " language=\"javascript\"\r\n" + " protocol_version=\"1.0\"\r\n" + " fileuri=\"file://" + file + "\"\r\n" + "/>"; printResponse(response); strategies.put("feature_get", new FeatureGetCommand(this)); strategies.put("feature_set", new FeatureSetCommand(this)); strategies.put("stdin", new StdInCommand(this)); strategies.put("stdout", new StdOutCommand(this)); strategies.put("stderr", new StdErrCommand(this)); strategies.put("run", new RunCommand(this)); strategies.put("context_names", new ContextNamesCommand(this)); strategies.put("stop", new StopCommand(this)); strategies.put("step_over", new StepOverCommand(this)); strategies.put("step_into", new StepIntoCommand(this)); strategies.put("step_out", new StepOutCommand(this)); strategies.put("breakpoint_get", new GetBreakPointCommand(this)); strategies.put("breakpoint_set", new SetBreakPointCommand(this)); strategies.put("breakpoint_remove", new RemoveBreakPointCommand(this)); strategies.put("breakpoint_update", new UpdateBreakPointCommand(this)); strategies.put("context_get", new ContextGetCommand(this)); strategies.put("property_set", new PropertySetCommand(this)); strategies.put("eval", new EvalCommand(this)); strategies.put("property_get", new PropertyGetCommand(this)); strategies.put("break", new BreakCommand(this)); strategies.put("stack_depth", new StackDepthCommand(this)); strategies.put("stack_get", new StackGetCommand(this)); } protected void printProperty(String id, String fullName, Object value, StringBuffer properties, int level, boolean addChilds) { boolean hasChilds = false; int numC = 0; String vlEncoded; String name_of_object_class = ""; String data_type = getDataType(value); if (value instanceof Scriptable) { hasChilds = true; StringBuffer stringBuffer = new StringBuffer(); Scriptable p = (Scriptable) value; value = stringBuffer; String nv = p.getClassName(); name_of_object_class = nv; if (p instanceof NativeJavaObject) { NativeJavaObject obj = (NativeJavaObject) p; Object unwrap = obj.unwrap(); if (unwrap instanceof Class) { nv = ((Class) unwrap).getName(); } else if (unwrap.getClass().isArray()) { nv = "Array"; // String string = unwrap.getClass().getName(); // int len = Array.getLength(unwrap); // int q = string.indexOf('['); // if (q != -1) // string = string.substring(0, q); // int q1 = string.indexOf(']'); // nv = string + "[" + len + "]"; // if (q1 != -1) // nv += string.substring(q1); } else { if (unwrap instanceof String) { nv = "JavaString " + '"' + unwrap.toString() + '"'; } else { String string = unwrap.toString(); nv = string; // nv = unwrap.getClass().getName() + "(" + string + // ")"; } } } else if (p instanceof Wrapper) { Wrapper wrapper = (Wrapper) p; Object wrapped = wrapper.unwrap(); if (wrapped == null) { nv = "Undefined"; } else if (!wrapped.getClass().isArray()) { // if it is an array let the normal array handling do the // job // now just do toString(); nv = wrapped.toString(); } } else if (p instanceof XMLObject) { nv = ((XMLObject) p).toString(); data_type = "XML"; } stringBuffer.append(Base64Helper.encodeString(nv)); if (addChilds) { HashSet duplicates = new HashSet(); Scriptable prototype = p; while (prototype != null) { numC += createChilds(fullName, level, stringBuffer, prototype, duplicates); prototype = prototype.getPrototype(); } } else { HashSet duplicates = new HashSet(); Scriptable prototype = p; while (prototype != null) { Object[] ids = null; if (prototype instanceof LazyInitScope) { ids = ((LazyInitScope) prototype).getInitializedIds(); } else { ids = prototype.getIds(); } for (int a = 0; a < ids.length; a++) { if (!duplicates.add(ids[a])) continue; Object pvalue = null; try { if (ids[a] instanceof Integer) { pvalue = p.get(((Integer) ids[a]).intValue(), p); } else pvalue = p.get(ids[a].toString(), p); } catch (Exception e) { // dont let the debugger crash. e.printStackTrace(); } if (!(pvalue instanceof Function)) // HACK because // ShowFunctionsAction // doesnt work because of // the lazy behavior of // plugins in Eclipse { numC++; } } prototype = prototype.getPrototype(); } } vlEncoded = stringBuffer.toString(); } else { if (!(value instanceof Undefined)) { if (value == UniqueTag.NOT_FOUND) { vlEncoded = ""; } else vlEncoded = Base64Helper.encodeString( value != null ? value.toString() : "null"); } else { vlEncoded = Base64Helper.encodeString("Undefined"); } if (value != null) name_of_object_class = value.getClass().getName(); } id = escapeHTML(id); fullName = escapeHTML(fullName); properties.append("<property\r\n" + " name=\"" + id + "\"\r\n" + " fullname=\"" + fullName + "\"\r\n" + " type=\"" + data_type + "\"\r\n" + " classname=\"" + name_of_object_class + "\"\r\n" + " constant=\"0\"\r\n" + " children=\"" + (hasChilds ? 1 : 0) + "\"\r\n" + " encoding=\"base64\"\r\n" + " numchildren=\"" + numC + "\">\r\n" + vlEncoded + "</property>\r\n"); } private static String escapeHTML(String content) { content = replace(content, '&', "&"); //$NON-NLS-1$ content = replace(content, '"', """); //$NON-NLS-1$ content = replace(content, '<', "<"); //$NON-NLS-1$ return replace(content, '>', ">"); //$NON-NLS-1$ } private static String replace(String text, char c, String s) { int previous = 0; int current = text.indexOf(c, previous); if (current == -1) return text; StringBuffer buffer = new StringBuffer(); while (current > -1) { buffer.append(text.substring(previous, current)); buffer.append(s); previous = current + 1; current = text.indexOf(c, previous); } buffer.append(text.substring(previous)); return buffer.toString(); } /** * @param fullName * @param level * @param stringBuffer * @param p * @param ids */ private int createChilds(String fullName, int level, StringBuffer stringBuffer, Scriptable p, HashSet duplicates) { Object[] ids = null; if (p instanceof LazyInitScope) { ids = ((LazyInitScope) p).getInitializedIds(); } else if (p instanceof ScriptableObject && !(p instanceof XMLObject) && !(p instanceof NativeArray)) { ids = ((ScriptableObject) p).getAllIds(); } else { ids = p.getIds(); } int counter = 0; for (int a = 0; a < ids.length; a++) { if (!duplicates.add(ids[a])) continue; Object pvalue = null; try { if (ids[a] instanceof Integer) { pvalue = p.get(((Integer) ids[a]).intValue(), p); } else pvalue = p.get(ids[a].toString(), p); } catch (Throwable e) { // dont let the debugger crash. e.printStackTrace(); } if (!(pvalue instanceof Function)) // HACK because // ShowFunctionsAction // doesnt work because of the lazy // behavior of plugins in Eclipse { counter++; if (ids[a] instanceof Integer) { printProperty(ids[a].toString(), fullName + "[" + ids[a] + "]", pvalue, stringBuffer, level + 1, false); } else { printProperty(ids[a].toString(), fullName + "." + ids[a], pvalue, stringBuffer, level + 1, false); } } } return counter; } private String getDataType(Object value) { String data_type = "Object"; if (value instanceof Function) { data_type = "function"; } else if (value instanceof NativeJavaArray) { data_type = "javaarray"; } else if (value instanceof NativeArray) { data_type = "array"; } else if (value instanceof NativeJavaObject) { data_type = "javaobject"; } else if (value instanceof NativeJavaClass) { data_type = "javaclass"; } else if (value instanceof String) { data_type = "string"; } else if (value instanceof Number) { data_type = "number"; } else if (value instanceof Boolean) { data_type = "boolean"; } else if (value instanceof Date) { data_type = "date"; } else if (value instanceof Undefined || value == null) { data_type = "undefined"; } else if (value instanceof Wrapper) { return getDataType(((Wrapper) value).unwrap()); } return data_type; } @Override public void run() { try { DataInputStream ds = new DataInputStream(socket.getInputStream()); StringBuffer buf = new StringBuffer(); Context.enter(); while (ds.available() >= 0) { int c = ds.read(); if (c < 0) break; if (c < 32) { String s = buf.toString(); int indexOf = s.indexOf(' '); if (indexOf != -1) { String commandId = buf.substring(0, indexOf); Command object = (Command) strategies.get(commandId); if (object == null) { System.err.println(commandId); continue; } HashMap options = new HashMap(); String result = buf.substring(indexOf + 1); String[] split = result.split(" "); try { for (int a = 0; a < split.length; a++) { options.put(split[a], split[++a]); } } catch (Exception e) { } object.parseAndExecute(result, options); } buf = new StringBuffer(); } else { buf.append((char) c); } } } catch (IOException e) { e.printStackTrace(); } } @Override public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) { return new DBGPDebugFrame(cx, fnOrScript, this); } @Override public void handleCompilationDone(Context cx, DebuggableScript fnOrScript, String source) { } @Override public void update(Observable arg0, Object arg1) { if (runTransctionId != null) printResponse("<response command=\"run\"\r\n" + "status=\"break\"" + " reason=\"ok\"" + " transaction_id=\"" + runTransctionId + "\">\r\n" + "</response>\r\n" + ""); } public void notifyEnd() { printResponse("<response command=\"run\"\r\n" + "status=\"stopped\"" + " reason=\"ok\"" + " transaction_id=\"" + runTransctionId + "\">\r\n" + "</response>\r\n" + ""); System.exit(0); } @Override public void access(String property, ScriptableObject object) { ArrayList list = (ArrayList) stackmanager.getManager() .getWatchPoints(property); if (list != null) { int size = list.size(); for (int a = 0; a < size; a++) { BreakPoint watchPoint = (BreakPoint) list.get(a); if (watchPoint != null) { if (watchPoint.enabled) if (watchPoint.isAccess) { String wkey = watchPoint.file + watchPoint.line; String s = (String) cache.get(object); if ((s != null) && (s.equals(wkey))) { stackmanager.getObserver().update(null, this); stackmanager.waitForNotify(); } } } } } } WeakHashMap cache = new WeakHashMap(); @Override public void modification(String property, ScriptableObject object) { ArrayList list = (ArrayList) stackmanager.getManager() .getWatchPoints(property); if (list != null && stackmanager.getStackDepth() > 0) { int size = list.size(); for (int a = 0; a < size; a++) { BreakPoint watchPoint = (BreakPoint) list.get(a); if (watchPoint != null) { if (watchPoint.enabled) { String sn = stackmanager.getStackFrame(0) .getSourceName(); int ln = stackmanager.getStackFrame(0).getLineNumber(); String key = sn + ln; String wkey = watchPoint.file + watchPoint.line; if (key.equals(wkey)) { cache.put(object, wkey); } if (watchPoint.isModification) { Object object2 = cache.get(object); if (object2 != null) if (object2.equals(wkey)) { stackmanager.getObserver().update(null, this); synchronized (this.stackmanager) { try { this.stackmanager.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } } } } public void setProperty(String name, String value) { } public void setSuspendOnExit(boolean parseBoolean) { stackmanager.setSuspendOnExit(parseBoolean); } public void setSuspendOnEntry(boolean parseBoolean) { stackmanager.setSuspendOnEntry(parseBoolean); } }