blob: 0d380b03a6902d4f9c7f494bca20c09efbe03c32 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2018 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.dbgp.internal.utils;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.dltk.compiler.util.Util;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.dbgp.IDbgpProperty;
import org.eclipse.dltk.dbgp.IDbgpSessionInfo;
import org.eclipse.dltk.dbgp.IDbgpStatus;
import org.eclipse.dltk.dbgp.breakpoints.IDbgpBreakpoint;
import org.eclipse.dltk.dbgp.exceptions.DbgpException;
import org.eclipse.dltk.dbgp.exceptions.DbgpProtocolException;
import org.eclipse.dltk.dbgp.internal.DbgpFeature;
import org.eclipse.dltk.dbgp.internal.DbgpProperty;
import org.eclipse.dltk.dbgp.internal.DbgpSessionInfo;
import org.eclipse.dltk.dbgp.internal.DbgpStackLevel;
import org.eclipse.dltk.dbgp.internal.DbgpStatus;
import org.eclipse.dltk.dbgp.internal.breakpoints.DbgpCallBreakpoint;
import org.eclipse.dltk.dbgp.internal.breakpoints.DbgpConditionalBreakpoint;
import org.eclipse.dltk.dbgp.internal.breakpoints.DbgpExceptionBreakpoint;
import org.eclipse.dltk.dbgp.internal.breakpoints.DbgpLineBreakpoint;
import org.eclipse.dltk.dbgp.internal.breakpoints.DbgpReturnBreakpoint;
import org.eclipse.dltk.dbgp.internal.breakpoints.DbgpWatchBreakpoint;
import org.eclipse.dltk.debug.core.DLTKDebugConstants;
import org.eclipse.osgi.util.NLS;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class DbgpXmlEntityParser extends DbgpXmlParser {
private static final IDbgpProperty[] NO_CHILDREN = new IDbgpProperty[0];
private static final String ENCODING_NONE = "none"; //$NON-NLS-1$
private static final String ENCODING_BASE64 = "base64"; //$NON-NLS-1$
public static final String TAG_PROPERTY = "property"; //$NON-NLS-1$
protected DbgpXmlEntityParser() {
}
private static final String ATTR_LEVEL = "level"; //$NON-NLS-1$
private static final String ATTR_CMDBEGIN = "cmdbegin"; //$NON-NLS-1$
private static final String ATTR_CMDEND = "cmdend"; //$NON-NLS-1$
private static final String ATTR_LINENO = "lineno"; //$NON-NLS-1$
private static final String ATTR_METHOD = "method"; //$NON-NLS-1$
private static final String ATTR_FILENAME = "filename"; //$NON-NLS-1$
private static final String ATTR_WHERE = "where"; //$NON-NLS-1$
private static Element[] getChildElements(Element elem, String name) {
final List<Element> result = new ArrayList<>();
final NodeList children = elem.getChildNodes();
for (int i = 0, length = children.getLength(); i < length; ++i) {
final Node childNode = children.item(i);
if (childNode instanceof Element) {
final Element child = (Element) childNode;
if (child.getTagName().equals(name)) {
result.add(child);
}
}
}
return result.toArray(new Element[result.size()]);
}
public static DbgpStackLevel parseStackLevel(Element element)
throws DbgpException {
int level = Integer.parseInt(element.getAttribute(ATTR_LEVEL));
String cmdBegin = element.getAttribute(ATTR_CMDBEGIN);
String cmdEnd = element.getAttribute(ATTR_CMDEND);
int beginLine = -1;
int beginColumn = -1;
int endLine = -1;
int endColumn = -1;
if (cmdBegin.length() != 0 && cmdEnd.length() != 0) {
beginLine = parseLine(cmdBegin);
beginColumn = parseColumn(cmdBegin);
endLine = parseLine(cmdEnd);
endColumn = parseColumn(cmdEnd);
}
int lineNumber = Integer.parseInt(element.getAttribute(ATTR_LINENO));
String methodName = element.getAttribute(ATTR_METHOD);
/**
* TODO Check ATTR_TYPE who knows when.
*
* According to the http://xdebug.org/docs-dbgp.php#stack-get
* <code>Valid values are "file" or "eval"</code>, but Tcl debugger also
* sends "source" and "console".
*/
final URI fileUri = parseURI(element.getAttribute(ATTR_FILENAME));
final String where = element.getAttribute(ATTR_WHERE);
return new DbgpStackLevel(fileUri, where, level, lineNumber, lineNumber,
methodName, beginLine, beginColumn, endLine, endColumn);
}
private static final String FILE_SCHEME_PREFIX = DLTKDebugConstants.FILE_SCHEME
+ ":///"; //$NON-NLS-1$
private static URI parseURI(String fileName) {
/*
* ActiveState python debugger on windows sends URI as
* "file:///C|/path/to/file.py" we need to convert it.
*/
if (fileName.startsWith(FILE_SCHEME_PREFIX)) {
final int pos = FILE_SCHEME_PREFIX.length();
if (fileName.length() > pos + 3) {
if (Character.isLetter(fileName.charAt(pos))
&& fileName.charAt(pos + 1) == '|'
&& fileName.charAt(pos + 2) == '/') {
fileName = fileName.substring(0, pos + 1) + ':'
+ fileName.substring(pos + 2);
}
}
}
try {
return URI.create(fileName);
} catch (IllegalArgumentException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
try {
return new URI(DLTKDebugConstants.UNKNOWN_SCHEME, Util.EMPTY_STRING,
Util.EMPTY_STRING, fileName);
} catch (URISyntaxException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
try {
return new URI(DLTKDebugConstants.UNKNOWN_SCHEME, Util.EMPTY_STRING,
Util.EMPTY_STRING, "unknown");//$NON-NLS-1$
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
private static final String ATTR_FEATURE_NAME = "feature_name"; //$NON-NLS-1$
private static final String ATTR_SUPPORTED = "supported"; //$NON-NLS-1$
public static DbgpFeature parseFeature(Element element)
throws DbgpProtocolException {
String name = element.getAttribute(ATTR_FEATURE_NAME);
boolean supported = makeBoolean(element.getAttribute(ATTR_SUPPORTED));
String value = parseContent(element);
return new DbgpFeature(supported, name, value);
}
private static final String ATTR_NAME = "name"; //$NON-NLS-1$
private static final String ATTR_FULLNAME = "fullname"; //$NON-NLS-1$
private static final String ATTR_TYPE = "type"; //$NON-NLS-1$
private static final String ATTR_CHILDREN = "children"; //$NON-NLS-1$
private static final String ATTR_NUMCHILDREN = "numchildren"; //$NON-NLS-1$
private static final String ATTR_CONSTANT = "constant"; //$NON-NLS-1$
private static final String ATTR_KEY = "key"; //$NON-NLS-1$
private static final String ATTR_PAGE = "page"; //$NON-NLS-1$
private static final String ATTR_PAGE_SIZE = "pagesize"; //$NON-NLS-1$
private static final String ATTR_ADDRESS = "address"; //$NON-NLS-1$
public static IDbgpProperty parseProperty(Element property) {
/*
* attributes: name, fullname, type, children, numchildren, constant,
* encoding, size, key, address
*/
// may exist as an attribute of the property or as child element
final String name = getFromChildOrAttr(property, ATTR_NAME);
final String fullName = getFromChildOrAttr(property, ATTR_FULLNAME);
final String type = property.getAttribute(ATTR_TYPE);
// hasChildren
boolean hasChildren = false;
if (property.hasAttribute(ATTR_CHILDREN)) {
hasChildren = makeBoolean(property.getAttribute(ATTR_CHILDREN));
}
// Children count
int childrenCount = -1;
if (property.hasAttribute(ATTR_NUMCHILDREN)) {
childrenCount = Integer
.parseInt(property.getAttribute(ATTR_NUMCHILDREN));
}
// Page
int page = 0;
if (property.hasAttribute(ATTR_PAGE)) {
page = Integer.parseInt(property.getAttribute(ATTR_PAGE));
}
// Page Size
int pagesize = -1;
if (property.hasAttribute(ATTR_PAGE_SIZE)) {
pagesize = Integer.parseInt(property.getAttribute(ATTR_PAGE_SIZE));
}
// Constant
boolean constant = false;
if (property.hasAttribute(ATTR_CONSTANT)) {
constant = makeBoolean(property.getAttribute(ATTR_CONSTANT));
}
// Key
String key = null;
if (property.hasAttribute(ATTR_KEY)) {
key = property.getAttribute(ATTR_KEY);
}
// memory address
String address = null;
if (property.hasAttribute(ATTR_ADDRESS)) {
address = property.getAttribute(ATTR_ADDRESS);
}
// Value
String value = ""; //$NON-NLS-1$
Element[] list = getChildElements(property, "value"); //$NON-NLS-1$
if (list.length == 0) {
value = getEncodedValue(property);
} else {
value = getEncodedValue(list[0]);
}
// Children
IDbgpProperty[] availableChildren = NO_CHILDREN;
if (hasChildren) {
final Element[] children = getChildElements(property, TAG_PROPERTY);
final int length = children.length;
if (length > 0) {
availableChildren = new IDbgpProperty[length];
for (int i = 0; i < length; ++i) {
availableChildren[i] = parseProperty(children[i]);
}
}
}
if (childrenCount < 0) {
childrenCount = availableChildren.length;
}
return new DbgpProperty(name, fullName, type, value, childrenCount,
hasChildren, constant, key, address, availableChildren, page,
pagesize);
}
private static final String ATTR_REASON = "reason"; //$NON-NLS-1$
private static final String ATTR_STATUS = "status"; //$NON-NLS-1$
public static IDbgpStatus parseStatus(Element element)
throws DbgpProtocolException {
String status = element.getAttribute(ATTR_STATUS);
String reason = element.getAttribute(ATTR_REASON);
return DbgpStatus.parse(status, reason);
}
private static final String LINE_BREAKPOINT = "line"; //$NON-NLS-1$
private static final String CALL_BREAKPOINT = "call"; //$NON-NLS-1$
private static final String RETURN_BREAKPOINT = "return"; //$NON-NLS-1$
private static final String EXCEPTION_BREAKPOINT = "exception"; //$NON-NLS-1$
private static final String CONDITIONAL_BREAKPOINT = "conditional"; //$NON-NLS-1$
private static final String WATCH_BREAKPOINT = "watch"; //$NON-NLS-1$
private static final String ATTR_ID = "id"; //$NON-NLS-1$
private static final String ATTR_STATE = "state"; //$NON-NLS-1$
private static final String ATTR_HIT_COUNT = "hit_count"; //$NON-NLS-1$
private static final String ATTR_HIT_VALUE = "hit_value"; //$NON-NLS-1$
private static final String ATTR_HIT_CONDITION = "hit_condition"; //$NON-NLS-1$
private static final String ATTR_LINE = "line"; //$NON-NLS-1$
private static final String ATTR_FUNCTION = "function"; //$NON-NLS-1$
private static final String ATTR_EXCEPTION = "exception"; //$NON-NLS-1$
private static final String ATTR_EXPRESSION = "expression"; //$NON-NLS-1$
public static IDbgpBreakpoint parseBreakpoint(Element element) {
// ActiveState Tcl
// ActiveState Python
// <response xmlns="urn:debugger_protocol_v1" command="breakpoint_get"
// transaction_id="1">
// <breakpoint id="1"
// type="line"
// filename="c:\distrib\dbgp\test\test1.py"
// lineno="8"
// state="enabled"
// temporary="0">
// </breakpoint>
// </response>
String type = element.getAttribute(ATTR_TYPE);
String id = element.getAttribute(ATTR_ID);
boolean enabled = element.getAttribute(ATTR_STATE).equals("enabled"); //$NON-NLS-1$
// not all dbgp implementations have these
int hitCount = getIntAttribute(element, ATTR_HIT_COUNT, 0);
int hitValue = getIntAttribute(element, ATTR_HIT_VALUE, 0);
String hitCondition = getStringAttribute(element, ATTR_HIT_CONDITION);
if (type.equals(LINE_BREAKPOINT)) {
String fileName = element.getAttribute(ATTR_FILENAME);
// ActiveState's dbgp implementation is slightly inconsistent
String lineno = element.getAttribute(ATTR_LINENO);
if ("".equals(lineno)) { //$NON-NLS-1$
lineno = element.getAttribute(ATTR_LINE);
}
int lineNumber = Integer.parseInt(lineno);
return new DbgpLineBreakpoint(id, enabled, hitValue, hitCount,
hitCondition, fileName, lineNumber);
} else if (type.equals(CALL_BREAKPOINT)) {
String function = element.getAttribute(ATTR_FUNCTION);
return new DbgpCallBreakpoint(id, enabled, hitValue, hitCount,
hitCondition, function);
} else if (type.equals(RETURN_BREAKPOINT)) {
String function = element.getAttribute(ATTR_FUNCTION);
return new DbgpReturnBreakpoint(id, enabled, hitValue, hitCount,
hitCondition, function);
} else if (type.equals(EXCEPTION_BREAKPOINT)) {
String exception = element.getAttribute(ATTR_EXCEPTION);
return new DbgpExceptionBreakpoint(id, enabled, hitValue, hitCount,
hitCondition, exception);
} else if (type.equals(CONDITIONAL_BREAKPOINT)) {
String expression = element.getAttribute(ATTR_EXPRESSION);
return new DbgpConditionalBreakpoint(id, enabled, hitValue,
hitCount, hitCondition, expression);
} else if (type.equals(WATCH_BREAKPOINT)) {
String expression = element.getAttribute(ATTR_EXPRESSION);
return new DbgpWatchBreakpoint(id, enabled, hitValue, hitCount,
hitCondition, expression);
}
return null;
}
private static final String ATTR_APPID = "appid"; //$NON-NLS-1$
private static final String ATTR_IDEKEY = "idekey"; //$NON-NLS-1$
private static final String ATTR_SESSION = "session"; //$NON-NLS-1$
private static final String ATTR_THREAD = "thread"; //$NON-NLS-1$
private static final String ATTR_PARENT = "parent"; //$NON-NLS-1$
private static final String ATTR_LANGUAGE = "language"; //$NON-NLS-1$
public static IDbgpSessionInfo parseSession(Element element) {
String appId = element.getAttribute(ATTR_APPID);
String ideKey = element.getAttribute(ATTR_IDEKEY);
String session = element.getAttribute(ATTR_SESSION);
String threadId = element.getAttribute(ATTR_THREAD);
String parentId = element.getAttribute(ATTR_PARENT);
String language = element.getAttribute(ATTR_LANGUAGE);
DbgpException error = DbgpXmlParser.checkError(element);
return new DbgpSessionInfo(appId, ideKey, session, threadId, parentId,
language, null, error);
}
protected static String getFromChildOrAttr(Element property, String name) {
Element[] list = getChildElements(property, name);
if (list.length == 0) {
return property.getAttribute(name);
}
/*
* this may or may not need to be base64 decoded - need to see output
* from an ActiveState's python debugging session to determine. gotta
* love protocol changes that have made their way back into the
* published spec
*/
return getEncodedValue(list[0]);
}
private static final String ATTR_ENCODING = "encoding"; //$NON-NLS-1$
protected static String getEncodedValue(Element element) {
String encoding = ENCODING_NONE;
if (element.hasAttribute(ATTR_ENCODING)) {
encoding = element.getAttribute(ATTR_ENCODING);
}
if (ENCODING_NONE.equals(encoding)) {
return parseContent(element);
}
if (ENCODING_BASE64.equals(encoding)) {
return parseBase64Content(element);
}
throw new AssertionError(NLS
.bind(Messages.DbgpXmlEntityParser_invalidEncoding, encoding));
}
}