| /******************************************************************************* |
| * 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)); |
| } |
| |
| } |