/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Author: atotic
* Created on Apr 22, 2004
*/
package org.python.pydev.debug.model;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.model.IStackFrame;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.core.PydevDebugPlugin;
import org.python.pydev.debug.newconsole.EvaluateDebugConsoleExpression;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Translate XML protocol responses into Py structures.
*
* Things get more complex than I'd like when complex Py structures get built.
*/
public class XMLUtils {
public static final SAXParserFactory parserFactory = SAXParserFactory.newInstance();
public static SAXParser getSAXParser() throws CoreException {
SAXParser parser = null;
try {
synchronized (parserFactory) {
parser = parserFactory.newSAXParser();
}
} catch (ParserConfigurationException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML SAX error", e));
} catch (SAXException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML SAX error", e));
}
return parser;
}
private static String decode(String value) {
if (value != null) {
try {
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
return null;
}
/**
* SAX parser for thread info
* <xml><thread name="name" id="id"/><xml>
*/
static class XMLToThreadInfo extends DefaultHandler {
public AbstractDebugTarget target;
public List<PyThread> threads = new ArrayList<PyThread>();
public XMLToThreadInfo(AbstractDebugTarget target) {
this.target = target;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (qName.equals("thread")) {
String name = attributes.getValue("name");
String id = attributes.getValue("id");
name = decode(name);
threads.add(new PyThread(target, name, id));
}
}
}
/**
* Creates IThread[] from the XML response
*/
static public PyThread[] ThreadsFromXML(AbstractDebugTarget target, String payload) throws CoreException {
try {
SAXParser parser = getSAXParser();
XMLToThreadInfo info = new XMLToThreadInfo(target);
parser.parse(new ByteArrayInputStream(payload.getBytes()), info);
return info.threads.toArray(new PyThread[0]);
} catch (CoreException e) {
throw e;
} catch (SAXException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error", e));
} catch (IOException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error", e));
}
}
/**
* Creates a variable from XML attributes
* <var name="self" type="ObjectType" value="<DeepThread>"/>
*/
static PyVariable createVariable(AbstractDebugTarget target, IVariableLocator locator, Attributes attributes) {
PyVariable var;
String name = attributes.getValue("name");
String type = attributes.getValue("type");
String value = attributes.getValue("value");
try {
if (value != null) {
value = URLDecoder.decode(value, "UTF-8");
}
} catch (Exception e) {
Log.log(e);
}
try {
if (name != null) {
name = URLDecoder.decode(name, "UTF-8");
}
} catch (Exception e) {
Log.log(e);
}
String isContainer = attributes.getValue("isContainer");
if ("True".equals(isContainer)) {
var = new PyVariableCollection(target, name, type, value, locator);
} else {
var = new PyVariable(target, name, type, value, locator);
}
String isRetVal = attributes.getValue("isRetVal");
if ("True".equals(isRetVal)) {
var.setIsReturnValue(true);
}
String isIPythonHidden = attributes.getValue("isIPythonHidden");
if ("True".equals(isIPythonHidden)) {
var.setIsIPythonHidden(true);
}
String isErrorOnEval = attributes.getValue("isErrorOnEval");
if ("True".equals(isErrorOnEval)) {
var.setIsErrorOnEval(true);
}
return var;
}
/**
* XMLToStack SAX traverse
*/
static class XMLToStackInfo extends DefaultHandler {
public PyThread thread;
public String stopReason;
public List<IStackFrame> stack = new ArrayList<IStackFrame>();
public List<PyVariable> locals;
public AbstractDebugTarget target;
PyStackFrame currentFrame;
public XMLToStackInfo(AbstractDebugTarget target) {
this.target = target;
}
private void startThread(Attributes attributes) throws SAXException {
String target_id = attributes.getValue("id");
thread = target.findThreadByID(target_id);
if (thread == null) {
throw new SAXException("Thread not found (" + target_id + ")"); // can happen when debugger has been destroyed
}
stopReason = attributes.getValue("stop_reason");
}
private void startFrame(Attributes attributes) {
String name = attributes.getValue("name");
String id = attributes.getValue("id");
String file = attributes.getValue("file");
try {
if (file != null) {
file = URLDecoder.decode(file, "UTF-8");
File tempFile = new File(file);
if (tempFile.exists()) {
file = FileUtils.getFileAbsolutePath(tempFile);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
String line = attributes.getValue("line");
IPath filePath = new Path(file);
// Try to recycle old stack objects (this is needed so that in a step over we
// reuse the same frame and keep the expanded state of the frame).
currentFrame = thread.findStackFrameByID(id);
if (currentFrame == null) {
currentFrame = new PyStackFrame(thread, id, name, filePath, Integer.parseInt(line), target);
} else {
currentFrame.setName(name);
currentFrame.setPath(filePath);
currentFrame.setLine(Integer.parseInt(line));
// If we found it, reuse it and make sure that new variables will be asked when requested.
currentFrame.forceGetNewVariables();
}
stack.add(currentFrame);
}
/**
* Assign stack frames to thread.
* Assign global variables to thread
* Assign local variables to stack frame
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
/*
<xml>
<thread id="id"/>
<frame id="id" name="functionName " file="file" line="line">
@deprecated: variables are no longer returned in this request (they are
gotten later in asynchronously to speed up the debugger).
<var scope="local" name="self" type="ObjectType" value="<DeepThread>"/>
</frame>*
*/
if (qName.equals("thread")) {
startThread(attributes);
} else if (qName.equals("frame")) {
startFrame(attributes);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
}
}
public static class StoppedStack {
public final PyThread thread;
public final String stopReason;
public final IStackFrame[] stack;
public StoppedStack(PyThread thread, String stopReason, IStackFrame[] stack) {
this.thread = thread;
this.stopReason = stopReason;
this.stack = stack;
}
}
/**
* @param payload
* @return an array of [thread_id, stopReason, IStackFrame[]]
*/
public static StoppedStack XMLToStack(AbstractDebugTarget target, String payload) throws CoreException {
IStackFrame[] stack = new IStackFrame[0];
StoppedStack retVal;
try {
SAXParser parser = getSAXParser();
XMLToStackInfo info = null;
try {
info = new XMLToStackInfo(target);
parser.parse(new ByteArrayInputStream(payload.getBytes()), info);
} catch (SAXParseException e) {
info = new XMLToStackInfo(target);
FastStringBuffer buf2 = fixXml(payload);
parser.parse(new ByteArrayInputStream(buf2.getBytes()), info);
Log.log("Received wrong xml which was fixed but indicates problem in the debugger in the server-side (please report error):\n"
+ payload, e);
}
stack = info.stack.toArray(new IStackFrame[0]);
retVal = new StoppedStack(info.thread, info.stopReason, stack);
} catch (CoreException e) {
throw e;
} catch (SAXException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error reading:"
+ payload, e));
} catch (IOException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected IO error reading xml:"
+ payload, e));
}
return retVal;
}
/**
* Try to fix a xml (which actually shouldn't happen): replace <, > and " on wrong places with < > and "
*/
public static FastStringBuffer fixXml(String payload) {
int length = payload.length();
FastStringBuffer buf2 = new FastStringBuffer(length + 10);
boolean inQuotes = false;
boolean inTag = false;
for (int i = 0; i < length; i++) {
char c = payload.charAt(i);
if (c == '"') {
if (inTag) {
inQuotes = !inQuotes;
buf2.append(c);
} else {
buf2.append(""");
}
} else if (c == '<') {
if (inQuotes) {
buf2.append("<");
} else {
inTag = true;
buf2.append(c);
}
} else if (c == '>') {
if (inQuotes) {
buf2.append(">");
} else {
inTag = false;
buf2.append(c);
}
} else {
buf2.append(c);
}
}
return buf2;
}
/**
* Processes CMD_GET_VARIABLE return
*
*/
static class XMLToVariableInfo extends DefaultHandler {
private AbstractDebugTarget target;
private IVariableLocator locator;
public List<PyVariable> vars;
public XMLToVariableInfo(AbstractDebugTarget target, IVariableLocator locator) {
this.target = target;
this.locator = locator;
vars = new ArrayList<PyVariable>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
// <var name="self" type="ObjectType" value="<DeepThread>"/>
// create a local variable, and add it to locals
if (qName.equals("var")) {
vars.add(createVariable(target, locator, attributes));
}
}
}
public static PyVariable[] XMLToVariables(AbstractDebugTarget target, IVariableLocator locator, String payload)
throws CoreException {
try {
SAXParser parser = getSAXParser();
XMLToVariableInfo info = new XMLToVariableInfo(target, locator);
parser.parse(new ByteArrayInputStream(payload.getBytes()), info);
PyVariable[] vars = new PyVariable[info.vars.size()];
for (int i = 0; i < info.vars.size(); i++) {
vars[i] = info.vars.get(i);
}
return vars;
} catch (CoreException e) {
throw e;
} catch (SAXException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error", e));
} catch (IOException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error", e));
}
}
// Processing referrers --------------------------------------------------------------------------------------------
/**
* Processes Custom command to get referrers.
*/
static class XMLToReferrersInfoHandler extends DefaultHandler {
private AbstractDebugTarget target;
public List<PyVariable> vars;
public PyVariable forVar;
private IVariableLocator locator;
private boolean inFor;
/**
* @param locationInDb How to access the variable searched in the debugger.
*/
public XMLToReferrersInfoHandler(AbstractDebugTarget target, final IVariableLocator locator) {
this.target = target;
this.locator = locator;
vars = new ArrayList<PyVariable>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
// <var name="self" type="ObjectType" value="<DeepThread>"/>
// create a local variable, and add it to locals
if (qName.equals("for")) {
inFor = true;
} else if (qName.equals("var")) {
PyVariable var = createVariable(target, locator, attributes);
//When we find a var for the referrers, usually we have the id and sometimes we can know how that
//variable is referenced in the container.
String id = attributes.getValue("id");
String foundAs = attributes.getValue("found_as");
try {
if (foundAs != null) {
foundAs = URLDecoder.decode(foundAs, "UTF-8");
}
} catch (Exception e) {
Log.log(e);
}
var.setRefererrerFoundInfo(id, foundAs);
if (inFor) {
forVar = var;
} else {
vars.add(var);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals("for")) {
inFor = false;
}
}
}
public static class XMLToReferrersInfo {
public final PyVariable forVar;
public final PyVariable[] vars;
public final AbstractDebugTarget target;
public XMLToReferrersInfo(AbstractDebugTarget target, PyVariable forVar, PyVariable[] vars) {
this.target = target;
this.forVar = forVar;
this.vars = vars;
}
}
/**
* May return null if there's some error in the processing.
*/
public static XMLToReferrersInfo XMLToReferrers(final AbstractDebugTarget target,
final IVariableLocator locationInDb,
String payload) {
try {
SAXParser parser = getSAXParser();
XMLToReferrersInfoHandler info = new XMLToReferrersInfoHandler(target, locationInDb);
parser.parse(new ByteArrayInputStream(payload.getBytes()), info);
PyVariable[] vars = info.vars.toArray(new PyVariable[info.vars.size()]);
return new XMLToReferrersInfo(target, info.forVar, vars);
} catch (Exception e) {
Log.log(e);
}
return null;
}
// Processing completions ------------------------------------------------------------------------------------------
/**
* Processes CMD_GET_COMPLETIONS return
*
*/
static class XMLToCompletionsInfo extends DefaultHandler {
public List<Object[]> completions;
public XMLToCompletionsInfo() {
completions = new ArrayList<Object[]>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
// <comp p0="%s" p1="%s" p2="%s" p3="%s"/>
if (qName.equals("comp")) {
Object[] comp = new Object[] { decode(attributes.getValue("p0")), decode(attributes.getValue("p1")),
decode(attributes.getValue("p2")), decode(attributes.getValue("p3")), };
completions.add(comp);
}
}
}
public static List<Object[]> convertXMLcompletionsFromConsole(String payload) throws CoreException {
try {
SAXParser parser = getSAXParser();
XMLToCompletionsInfo info = new XMLToCompletionsInfo();
parser.parse(new ByteArrayInputStream(payload.getBytes()), info);
return info.completions;
} catch (CoreException e) {
throw e;
} catch (SAXException e) {
throw new CoreException(
PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error. Payload:\n" + payload, e));
} catch (IOException e) {
throw new CoreException(
PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error. Payload:\n" + payload, e));
}
}
/**
* Creates an object of
* EvaluateDebugConsoleExpression.PydevDebugConsoleMessage. Parse the XML in
* the below mentioned format
* <xml>
* <output message = console_output_message></output>
* <error message = console_error_message></error>
* <more>true/false</more>
* </xml>
*
* @author hussain.bohra
*/
static class DebugConsoleMessageInfo extends DefaultHandler {
private EvaluateDebugConsoleExpression.PydevDebugConsoleMessage debugConsoleMessage;
private String attrValue;
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
attrValue = new String(ch, start, length);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
boolean isError = true;
if (qName.equalsIgnoreCase("MORE")) {
return;
}
if (qName.equalsIgnoreCase("OUTPUT")) {
isError = false;
}
for (int i = 0; i < attributes.getLength(); i++) {
if (attributes.getQName(i).equalsIgnoreCase("MESSAGE")) {
String outputMessage = attributes.getValue(i);
debugConsoleMessage.appendMessage(outputMessage, isError);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equalsIgnoreCase("MORE")) {
if (attrValue.equals("True")) {
debugConsoleMessage.setMore(true);
} else {
debugConsoleMessage.setMore(false);
}
}
}
public DebugConsoleMessageInfo() {
debugConsoleMessage = new EvaluateDebugConsoleExpression.PydevDebugConsoleMessage();
}
}
/**
* Get an instance of a SAXParser and create a new DebugConsoleMessageInfo object.
*
* Call the parser passing it a DebugConsoleMessageInfo Object
*
* @param payload
* @return
* @throws CoreException
*/
public static EvaluateDebugConsoleExpression.PydevDebugConsoleMessage getConsoleMessage(String payload)
throws CoreException {
EvaluateDebugConsoleExpression.PydevDebugConsoleMessage debugConsoleMessage = new EvaluateDebugConsoleExpression.PydevDebugConsoleMessage();
try {
SAXParser parser = getSAXParser();
DebugConsoleMessageInfo info = new DebugConsoleMessageInfo();
parser.parse(new ByteArrayInputStream(payload.getBytes()), info);
debugConsoleMessage = info.debugConsoleMessage;
} catch (SAXException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error. Payload: "
+ payload, e));
} catch (IOException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error", e));
}
return debugConsoleMessage;
}
/**
* Create a List<ExceptionStackTrace> from the received XML in the below mentioned
* format:
* <xml>
* <frame
* thread_id = "pid1388_seq2"
* file="test\test.py"
* line="1332"
* name="func"
* obj="old_f(*args, **kwargs)" />
* </xml>
*
* @author hussain.bohra
*/
static class ExceptionStackTraceXMLInfo extends DefaultHandler {
private List<PyConditionalBreakPointManager.ExceptionStackTrace> exceptionStackTraceList;
private PyConditionalBreakPointManager.ExceptionStackTrace exceptionStackTrace;
//private String attrValue;
private AbstractDebugTarget target;
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
//attrValue = new String(ch, start, length);
}
/**
* Create a new ExceptionStackTrace Object on encountering <frame> tag.
* Adds an object to main list
*
* Identify thread_id, filename, line, name and methodObj from xml and
* creates a new ExceptionStackTrace Object
*
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase("FRAME")) {
String filename = "";
String name = "";
String methodObj = "";
int line = 0;
for (int i = 0; i < attributes.getLength(); i++) {
if (attributes.getQName(i).equalsIgnoreCase("THREAD_ID")) {
//Ignore for now.
} else if (attributes.getQName(i).equalsIgnoreCase("FILE")) {
filename = attributes.getValue(i);
} else if ((attributes.getQName(i).equalsIgnoreCase("LINE"))) {
line = Integer.parseInt(attributes.getValue(i));
} else if ((attributes.getQName(i).equalsIgnoreCase("NAME"))) {
name = attributes.getValue(i);
} else if ((attributes.getQName(i).equalsIgnoreCase("OBJ"))) {
methodObj = attributes.getValue(i);
}
}
//PyThread pyThread = target.findThreadByID(threadId);
//if (pyThread == null) {
// // can happen when debugger has been destroyed
// throw new SAXException("Thread not found (" + threadId + ")");
//}
exceptionStackTrace = new PyConditionalBreakPointManager.ExceptionStackTrace(
target, filename, line, name, methodObj);
exceptionStackTraceList.add(exceptionStackTrace);
}
}
public ExceptionStackTraceXMLInfo(AbstractDebugTarget target) {
this.exceptionStackTraceList = new ArrayList<PyConditionalBreakPointManager.ExceptionStackTrace>();
this.target = target;
}
}
/**
* Get an instance of a SAXParser and create a new ExceptionStackTraceXMLInfo object.
*
* Call the parser passing it an ExceptionStackTraceXMLInfo Object
*
* @param payload
* @param AbstractDebugTarget target
* @return
* @throws CoreException
*/
public static List<PyConditionalBreakPointManager.ExceptionStackTrace> getExceptionStackTrace(
AbstractDebugTarget target, String payload) throws CoreException {
List<PyConditionalBreakPointManager.ExceptionStackTrace> exceptionStackTraceList = new ArrayList<PyConditionalBreakPointManager.ExceptionStackTrace>();
try {
SAXParser parser = getSAXParser();
ExceptionStackTraceXMLInfo info = new ExceptionStackTraceXMLInfo(target);
parser.parse(new ByteArrayInputStream(payload.getBytes(StandardCharsets.UTF_8)), info);
exceptionStackTraceList = info.exceptionStackTraceList;
} catch (SAXException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error", e));
} catch (IOException e) {
throw new CoreException(PydevDebugPlugin.makeStatus(IStatus.ERROR, "Unexpected XML error", e));
}
return exceptionStackTraceList;
}
}