From 0449ae3c417df7814949c1c8626f8e56cdc2aaf4 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 10 Jul 2012 15:38:21 -0400 Subject: [PATCH] Bug 381754: Group-expression support for Expressions view. A group-expression is a comma-separated list of expressions. Each expression can also use glob-pattern matching. Currently, the matching is only supported for registers and the single '*' expressions which will show all local variables. Change-Id: I09f505dcd453d9ce0301bbebb8d4c459316b9224 Reviewed-on: https://git.eclipse.org/r/6549 Reviewed-by: Marc Khouzam IP-Clean: Marc Khouzam Tested-by: Marc Khouzam --- .../GDBPatternMatchingExpressions.java | 848 ++++++++++++++++++ .../gdb/service/GdbDebugServicesFactory.java | 9 +- .../eclipse/cdt/dsf/gdb/service/Messages.java | 4 + .../cdt/dsf/gdb/service/Messages.properties | 6 +- .../IGDBPatternMatchingExpressions.java | 20 + .../cdt/dsf/mi/service/MIExpressions.java | 19 +- 6 files changed, 897 insertions(+), 9 deletions(-) create mode 100644 dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBPatternMatchingExpressions.java create mode 100644 dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/IGDBPatternMatchingExpressions.java diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBPatternMatchingExpressions.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBPatternMatchingExpressions.java new file mode 100644 index 00000000000..fd3e5e03253 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GDBPatternMatchingExpressions.java @@ -0,0 +1,848 @@ +/******************************************************************************* + * Copyright (c) 2012 Ericsson 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: + * Marc Khouzam (Ericsson) - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.dsf.gdb.service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.cdt.core.IAddress; +import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.RequestMonitor; +import org.eclipse.cdt.dsf.datamodel.CompositeDMContext; +import org.eclipse.cdt.dsf.datamodel.DMContexts; +import org.eclipse.cdt.dsf.datamodel.IDMContext; +import org.eclipse.cdt.dsf.debug.service.ICachingService; +import org.eclipse.cdt.dsf.debug.service.IExpressions; +import org.eclipse.cdt.dsf.debug.service.IExpressions2; +import org.eclipse.cdt.dsf.debug.service.IExpressions3; +import org.eclipse.cdt.dsf.debug.service.IFormattedValues; +import org.eclipse.cdt.dsf.debug.service.IRegisters; +import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterDMContext; +import org.eclipse.cdt.dsf.debug.service.IRegisters.IRegisterGroupDMContext; +import org.eclipse.cdt.dsf.debug.service.IStack; +import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext; +import org.eclipse.cdt.dsf.debug.service.IStack.IVariableDMContext; +import org.eclipse.cdt.dsf.debug.service.IStack.IVariableDMData; +import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; +import org.eclipse.cdt.dsf.mi.service.IGDBPatternMatchingExpressions; +import org.eclipse.cdt.dsf.mi.service.IMIExpressions; +import org.eclipse.cdt.dsf.mi.service.MIRegisters.MIRegisterDMC; +import org.eclipse.cdt.dsf.service.AbstractDsfService; +import org.eclipse.cdt.dsf.service.DsfSession; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.BundleContext; + +import com.ibm.icu.text.MessageFormat; + +/** + * Expressions service added as a layer above the standard Expressions service. + * This layer allows to support group-expressions and glob-pattern matching. + * Group-expressions give the user the ability to create a comma-separated + * list of expressions in a single entry. + * Glob-patterns are a way to specify a set of expressions that match the + * pattern. + * @since 4.2 + */ +public class GDBPatternMatchingExpressions extends AbstractDsfService implements IGDBPatternMatchingExpressions, ICachingService { + /** + * A regex representing each character that can be used to separate + * the different expressions contained in a group-expression. + * The [] are not part the characters, but are used in the regex format. + * Note that we don't allow a space separator because spaces are valid within + * an expression (e.g., i + 1) + */ + private final static String GROUP_EXPRESSION_SEPARATORS_REGEXP = "[,;]"; //$NON-NLS-1$ + + /** + * A group-expression is an expression that requires expansion into a (potentially empty) + * list of sub-expressions. Using a group-expression allows the user to create groups + * of expressions very quickly. + * + * We support two aspects for group-expressions: + * 1- The glob syntax (http://www.kernel.org/doc/man-pages/online/pages/man7/glob.7.html) + * This allows to user to specify glob-patterns to match different expressions. + * 2- Comma-separated expressions, each potentially using the glob-syntax + */ + protected static class GroupExpressionDMC implements IExpressionDMContext { + + /** + * The expression context, as created by the main Expression service. + * We delegate the handling of the expression to it. + */ + private IExpressionDMContext fExprDelegate; + + /** + * The set of expressions making up the group expression. + * This list is the result of splitting the original expression + * and then trimming each resulting expression. + */ + private List fExpressionsInGroup = null; + + public GroupExpressionDMC(IExpressionDMContext exprDmc) { + fExprDelegate = exprDmc; + } + + @Override + public String getExpression() { + return fExprDelegate.getExpression(); + } + + /** + * Returns an array representing the different expressions + * that make up this group-expression. + */ + public List getExpressionsInGroup() { + if (fExpressionsInGroup == null) { + // Split the list + String[] splitExpressions = getExpression().split(GROUP_EXPRESSION_SEPARATORS_REGEXP); + + // Remove any extra whitespace from each resulting expression, + // and ignore any empty expressions. + fExpressionsInGroup = new ArrayList(splitExpressions.length); + for (String expr : splitExpressions) { + expr = expr.trim(); + if (!expr.isEmpty()) { + fExpressionsInGroup.add(expr); + } + } + } + return fExpressionsInGroup; + } + + @Override + public String getSessionId() { + return fExprDelegate.getSessionId(); + } + + @Override + public IDMContext[] getParents() { + return fExprDelegate.getParents(); + }; + + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class adapterType) { + return fExprDelegate.getAdapter(adapterType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof GroupExpressionDMC)) return false; + + return ((GroupExpressionDMC)obj).fExprDelegate.equals(fExprDelegate); + } + + @Override + public int hashCode() { + return fExprDelegate.hashCode(); + } + } + + /** + * The model data interface for group-expressions + */ + protected static class GroupExpressionDMData implements IExpressionDMDataExtension { + private final String fRelativeExpression; + private final int fNumChildren; + + public GroupExpressionDMData(String expr, int numChildren) { + assert expr != null; + + fRelativeExpression = expr; + fNumChildren = numChildren; + } + + @Override + public String getName() { + return fRelativeExpression; + } + + @Override + public BasicType getBasicType() { + return IExpressionDMData.BasicType.array; + } + + @Override + public String getTypeName() { + return Messages.GroupPattern; + } + + @Override + public String getEncoding() { + return null; + } + + @Override + public String getTypeId() { + return null; + } + + @Override + public Map getEnumerations() { + return new HashMap(); + } + + @Override + public IRegisterDMContext getRegister() { + return null; + } + + @Override + public boolean hasChildren() { + return fNumChildren > 0; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof GroupExpressionDMData)) return false; + return fRelativeExpression.equals(((GroupExpressionDMData)other).fRelativeExpression); + } + + @Override + public int hashCode() { + return fRelativeExpression.hashCode(); + } + + @Override + public String toString() { + return "GroupExpr: " + fRelativeExpression; //$NON-NLS-1$ + } + } + + /** + * The base expression service to which we delegate all non-group-expression logic. + */ + private IMIExpressions fDelegate; + + public GDBPatternMatchingExpressions(DsfSession session, IExpressions delegate) { + super(session); + fDelegate = (IMIExpressions)delegate; + } + + @Override + public void initialize(final RequestMonitor requestMonitor) { + super.initialize( + new ImmediateRequestMonitor(requestMonitor) { + @Override + public void handleSuccess() { + doInitialize(requestMonitor); + }}); + } + + private void doInitialize(final RequestMonitor requestMonitor) { + // Our delegate should not be initialized yet, as we have no + // good way to unregister it. + assert !fDelegate.isRegistered(); + + // We must first register this service to let the original + // expression service know that it should not register itself. + register(new String[] { IExpressions.class.getName(), + IExpressions2.class.getName(), + IExpressions3.class.getName(), + IMIExpressions.class.getName() }, + new Hashtable()); + + // Second, we initialize the delegate so it can perform its duties + fDelegate.initialize(requestMonitor); + } + + @Override + public void shutdown(final RequestMonitor requestMonitor) { + fDelegate.shutdown(new RequestMonitor(getExecutor(), requestMonitor) { + @Override + protected void handleSuccess() { + unregister(); + GDBPatternMatchingExpressions.super.shutdown(requestMonitor); + } + }); + } + + @Override + protected BundleContext getBundleContext() { + return GdbPlugin.getBundleContext(); + } + + @Override + public IExpressionDMContext createExpression(IDMContext ctx, String expression) { + IExpressionDMContext expressionDmc = fDelegate.createExpression(ctx, expression); + + if (isGroupExpression(expression)) { + return new GroupExpressionDMC(expressionDmc); + } else { + return expressionDmc; + } + } + + @Override + public ICastedExpressionDMContext createCastedExpression(IExpressionDMContext context, CastInfo castInfo) { + // Cannot cast a GroupExpression + assert (!(context instanceof GroupExpressionDMC)); + + return fDelegate.createCastedExpression(context, castInfo); + } + + @Override + public void getExpressionDataExtension(final IExpressionDMContext dmc, final DataRequestMonitor rm) { + if (dmc instanceof GroupExpressionDMC) { + getSubExpressionCount(dmc, new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + rm.done(new GroupExpressionDMData(((GroupExpressionDMC)dmc).getExpression(), getData())); + } + }); + return; + } + + fDelegate.getExpressionDataExtension(dmc, rm); + } + + + @Override + public void getExpressionData(final IExpressionDMContext dmc, final DataRequestMonitor rm) { + if (dmc instanceof GroupExpressionDMC) { + getSubExpressionCount(dmc, new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + rm.done(new GroupExpressionDMData(((GroupExpressionDMC)dmc).getExpression(), getData())); + } + }); + return; + } + + fDelegate.getExpressionData(dmc, rm); + } + + @Override + public void getExpressionAddressData(IExpressionDMContext dmc, DataRequestMonitor rm) { + // A GroupExpression does not have an address + if (dmc instanceof GroupExpressionDMC) { + rm.done(new IExpressionDMLocation() { + @Override + public IAddress getAddress() { + return IExpressions.IExpressionDMLocation.INVALID_ADDRESS; + } + @Override + public int getSize() { + return 0; + } + @Override + public String getLocation() { + return ""; //$NON-NLS-1$ + } + }); + return; + } + + fDelegate.getExpressionAddressData(dmc, rm); + } + + @Override + public void getSubExpressions(IExpressionDMContext exprCtx, DataRequestMonitor rm) { + if (exprCtx instanceof GroupExpressionDMC) { + matchGroupExpression((GroupExpressionDMC)exprCtx, -1, -1, rm); + } else { + fDelegate.getSubExpressions(exprCtx, rm); + } + } + + @Override + public void getSubExpressions(IExpressionDMContext exprCtx, int startIndex, int length, DataRequestMonitor rm) { + if (exprCtx instanceof GroupExpressionDMC) { + matchGroupExpression((GroupExpressionDMC)exprCtx, startIndex, length, rm); + } else { + fDelegate.getSubExpressions(exprCtx, startIndex, length, rm); + } + } + + @Override + public void getSubExpressionCount(IExpressionDMContext dmc, final DataRequestMonitor rm) { + if (dmc instanceof GroupExpressionDMC) { + matchGroupExpression((GroupExpressionDMC)dmc, -1, -1, new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + rm.done(getData().length); + } + }); + } else { + fDelegate.getSubExpressionCount(dmc, rm); + } + } + + @Override + public void getSubExpressionCount(IExpressionDMContext dmc, int maxNumberOfChildren, final DataRequestMonitor rm) { + if (dmc instanceof GroupExpressionDMC) { + // No need to worry about maxNumberOfChildren for the case of a group-expression, since there won't be + // a very large amount of them. + matchGroupExpression((GroupExpressionDMC)dmc, -1, -1, new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + rm.done(getData().length); + } + }); + } else { + fDelegate.getSubExpressionCount(dmc, maxNumberOfChildren, rm); + } + } + + @Override + public void getBaseExpressions(IExpressionDMContext exprContext, DataRequestMonitor rm) { + rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Not supported", null)); //$NON-NLS-1$ + } + + @Override + public void canWriteExpression(IExpressionDMContext dmc, DataRequestMonitor rm) { + // A GroupExpression's value cannot be modified + if (dmc instanceof GroupExpressionDMC) { + rm.done(false); + return; + } + + fDelegate.canWriteExpression(dmc, rm); + } + + @Override + public void writeExpression(IExpressionDMContext dmc, String expressionValue, String formatId, RequestMonitor rm) { + // A GroupExpression's value cannot be modified + assert !(dmc instanceof GroupExpressionDMC); + fDelegate.writeExpression(dmc, expressionValue, formatId, rm); + } + + @Override + public void getAvailableFormats(IFormattedDataDMContext dmc, DataRequestMonitor rm) { + //For a group expression, we only show the NATURAL format + if (dmc instanceof GroupExpressionDMC) { + rm.done(new String[] { IFormattedValues.NATURAL_FORMAT }); + return; + } + + fDelegate.getAvailableFormats(dmc, rm); + } + + @Override + public FormattedValueDMContext getFormattedValueContext(IFormattedDataDMContext dmc, String formatId) { + // No special handling for GroupExpressions + return fDelegate.getFormattedValueContext(dmc, formatId); + } + + @Override + public void getFormattedExpressionValue(FormattedValueDMContext dmc, final DataRequestMonitor rm) { + GroupExpressionDMC groupExpr = DMContexts.getAncestorOfType(dmc, GroupExpressionDMC.class); + if (groupExpr != null) { + getSubExpressionCount(groupExpr, new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + int numChildren = getData(); + String value; + if (numChildren == 0) { + value = Messages.NoMatches; + } else if (numChildren == 1) { + value = MessageFormat.format(Messages.UniqueMatch, numChildren); + } else { + value = MessageFormat.format(Messages.UniqueMatches, numChildren); + } + rm.done(new FormattedValueDMData(value)); + } + }); + return; + } + + fDelegate.getFormattedExpressionValue(dmc, rm); + } + + @Override + public void safeToAskForAllSubExpressions(IExpressionDMContext dmc, DataRequestMonitor rm) { + // Always safe to ask for all sub-expression of a group expression, since we don't expect large + // amounts of children + if (dmc instanceof GroupExpressionDMC) { + rm.done(true); + return; + } + + fDelegate.safeToAskForAllSubExpressions(dmc, rm); + } + + @Override + public void flushCache(IDMContext context) { + if (fDelegate instanceof ICachingService) { + ((ICachingService)fDelegate).flushCache(context); + } + } + + /** + * Verify if we are dealing with a group expression. + * @param expr The expression to verify + * @return True if expr is a group expression. A group + * expression is either a comma-separated list of + * expressions, or an expression using a glob-pattern + */ + protected boolean isGroupExpression(String expr) { + // First check for a comma separated list of expression + // We want to re-use the regex that defines our separators, and we need to check + // if the expression contains that regex. I didn't find a method that + // checks if a string contains a regex, so instead we all any character before + // and after the regex, which achieves what we want. + // Note that checking if expr.split(regex) is bigger than 1, will not notice + // an expression that has a separator only at the end. + if (expr.matches(".*" + GROUP_EXPRESSION_SEPARATORS_REGEXP +".*")) { //$NON-NLS-1$ //$NON-NLS-2$ + // We are dealing with a group expression. + // It may not be a valid one, but it is one nonetheless. + return true; + } + + // Not a comma-separated list. Check if we are dealing with a glob-pattern. + return isGlobPattern(expr); + } + + /** + * Verify if we are dealing with a glob-pattern. + * We support the expression * which will match all local variables. + * We support glob-patterns for registers (must start with $) + * @param expr The expression to verify + * @return True if expr is a glob-pattern we support. + */ + protected boolean isGlobPattern(String expr) { + // Get rid of useless whitespace + expr = expr.trim(); + + // We support the glob-pattern '*' to indicate all local variables + if (expr.equals("*")) { //$NON-NLS-1$ + return true; + } + + // We only support glob-expressions for registers at this time + if (expr.startsWith("$")) { //$NON-NLS-1$ + // see: 'man glob' + if (expr.indexOf('*') != -1 || expr.indexOf('?') != -1 || expr.indexOf('[') != -1) { + return true; + } + } + + return false; + } + + /** + * Find all expressions that match the specified group-expression. + * This method retains the order of the expressions in the group-expression, to show them + * in the same order as the one specified by the user. The match of each expression in the group + * is sorted alphabetically however. + * + * @param groupExprDmc The group-expression context for which we want the matches (sub-expressions) + * @param startIndex The beginning of the range of matches (-1 means all matches) + * @param length The length of the range of matches (-1 means all matches) + * @param rm RequestMonitor that will contain the range of found matches. + */ + protected void matchGroupExpression(final GroupExpressionDMC groupExprDmc, int startIndex, int length, + final DataRequestMonitor rm) { + // First separate the group into different expressions. + // We need to create a new list, as we will modify it during our processing. + final List exprList = new ArrayList(groupExprDmc.getExpressionsInGroup()); + + // List to store the final result, which is all the sub-expressions of this group + final ArrayList subExprList = new ArrayList(); + + final int startIndex1 = (startIndex < 0) ? 0 : startIndex; + final int length1 = (length < 0) ? Integer.MAX_VALUE : length; + + matchExpressionList(exprList, subExprList, groupExprDmc, new ImmediateRequestMonitor(rm) { + @Override + protected void handleSuccess() { + // It would be nice to allow identical elements, so that the user + // can control their positioning. For example, the pattern $eax, $*, would show + // the $eax first, followed by all other registers sorted alphabetically. In that case + // $eax will be shown again within $*, but that would be ok. + // However, the platform does not handle the same element being there twice. + // Not only does selecting the element jump back and forth between the duplicates, + // but children of duplicated elements are not always right. Because of this, we + // remove all duplicates here. + LinkedHashSet uniqueSubExprSet = new LinkedHashSet(subExprList); + subExprList.clear(); + subExprList.addAll(uniqueSubExprSet); + + // Extract the range of interest from the final list + int endIndex = Math.min(startIndex1 + length1, subExprList.size()); + List subExprRangeList = subExprList.subList(startIndex1, endIndex); + IExpressionDMContext[] subExprRange = subExprRangeList.toArray(new IExpressionDMContext[subExprRangeList.size()]); + rm.done(subExprRange); + } + }); + } + + /** + * We use this recursive method to serialize the request for matches. Once one request is done, + * we create a new one. This allows us to guarantee that the resulting matches will + * be ordered in the same way every time. + */ + private void matchExpressionList(final List exprList, final List subExprList, final IDMContext parentDmc, + final RequestMonitor rm) { + // We've finished parsing the list + if (exprList.isEmpty()) { + rm.done(); + return; + } + + // Remove the next element from the list and process it. We handle glob-pattern matching if needed + // and sort the result alphabetically in that case. + String expr = exprList.remove(0); + + if (isGlobPattern(expr)) { + IExpressionDMContext exprDmc = createExpression(parentDmc, expr); + matchGlobExpression(exprDmc, new ImmediateDataRequestMonitor>(rm) { + @Override + protected void handleSuccess() { + List matches = getData(); + // Sort the matches to be more user-friendly + Collections.sort(matches, new Comparator() { + @Override + public int compare(IExpressionDMContext o1, IExpressionDMContext o2) { + return o1.getExpression().compareTo(o2.getExpression()); + } + }); + + subExprList.addAll(matches); + // Match the next expression from the list + matchExpressionList(exprList, subExprList, parentDmc, rm); + } + }); + } else { + // Just a normal expression + subExprList.add(createExpression(parentDmc, expr)); + // Match the next expression from the list + matchExpressionList(exprList, subExprList, parentDmc, rm); + } + } + + /** + * Find all expressions that match the specified glob-pattern. + * + * @param exprDmc The expression context for which we want the matches (sub-expressions) + * @param rm RequestMonitor that will contain the matches. + */ + protected void matchGlobExpression(final IExpressionDMContext exprDmc, final DataRequestMonitor> rm) { + final String fullExpr = exprDmc.getExpression().trim(); + + if (fullExpr.equals("*")) { //$NON-NLS-1$ + matchLocals(exprDmc, rm); + return; + } + + // Currently, we only support glob-expressions for registers, so + // we only need to match the glob-expression with register names. + // We should not arrive here if we are not handling a register + assert fullExpr.startsWith("$"); //$NON-NLS-1$ + + final IRegisters registerService = getServicesTracker().getService(IRegisters.class); + if (registerService == null) { + rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Register service unavailable", null)); //$NON-NLS-1$ + return; + } + + registerService.getRegisterGroups(exprDmc, new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + registerService.getRegisters( + new CompositeDMContext(new IDMContext[] { getData()[0], exprDmc } ), + new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + assert getData() instanceof MIRegisterDMC[]; + ArrayList matches = new ArrayList(); + for (MIRegisterDMC register : (MIRegisterDMC[])getData()) { + String potentialMatch = "$"+register.getName(); //$NON-NLS-1$ + if (globMatches(fullExpr, potentialMatch)) { + matches.add(createExpression(exprDmc, potentialMatch)); + } + } + + rm.done(matches); + } + }); + } + }); + } + + + /** + * Find all local variables that match the specified glob-pattern. + * We currently only support matching all local variables using the '*' pattern. + * + * @param globDmc The glob-expression context for which we want the matches (sub-expressions) + * @param rm RequestMonitor that will contain the matches. + */ + + protected void matchLocals(final IExpressionDMContext globDmc, final DataRequestMonitor> rm) { + // We only support '*' for local variables at this time + assert globDmc.getExpression().equals("*"); //$NON-NLS-1$ + + final IStack stackService = getServicesTracker().getService(IStack.class); + if (stackService == null) { + rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Stack service unavailable", null)); //$NON-NLS-1$ + return; + } + + IFrameDMContext frameCtx = DMContexts.getAncestorOfType(globDmc, IFrameDMContext.class); + if (frameCtx == null) { + rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Stack frame unavailable", null)); //$NON-NLS-1$ + return; + } + + stackService.getLocals(frameCtx, new ImmediateDataRequestMonitor(rm) { + @Override + protected void handleSuccess() { + IVariableDMContext[] localsDMCs = getData(); + final IVariableDMData[] localsDMData = new IVariableDMData[localsDMCs.length]; + + final CountingRequestMonitor crm = new CountingRequestMonitor(getExecutor(), rm) { + @Override + public void handleSuccess() { + ArrayList expressionDMCs = new ArrayList(localsDMData.length); + + for (IVariableDMData localDMData : localsDMData) { + expressionDMCs.add(createExpression(globDmc, localDMData.getName())); + } + rm.done(expressionDMCs); + } + }; + int countRM = 0; + + for (int index=0; index < localsDMCs.length; index++) { + final int finalIndex = index; + stackService.getVariableData(localsDMCs[finalIndex], new ImmediateDataRequestMonitor(crm) { + @Override + public void handleSuccess() { + localsDMData[finalIndex] = getData(); + crm.done(); + } + }); + + countRM++; + } + crm.setDoneCount(countRM); + } + }); + } + + /** + * Verify if the potentialMatch variable matches the glob-pattern. + * + * @param globPattern The glob-pattern to match + * @param potentialMatch The string that must match globPattern. + * @return True of potentialMatch does match globPattern. + */ + protected boolean globMatches(String globPattern, String potentialMatch) { + // Convert the glob-pattern into java regex to do the matching + + boolean inBrackets = false; + char[] patternArray = globPattern.toCharArray(); + char[] resultArray = new char[patternArray.length * 2 + 2]; + int pos = 0; + + // Must match from the very beginning + resultArray[pos++] = '^'; + for (int i = 0; i < patternArray.length; i++) { + switch(patternArray[i]) { + case '?': + if (inBrackets) { + resultArray[pos++] = '?'; + } else { + resultArray[pos++] = '.'; + } + break; + + case '*': + if (!inBrackets) { + resultArray[pos++] = '.'; + } + resultArray[pos++] = '*'; + break; + + case '-': + if (!inBrackets) { + resultArray[pos++] = '\\'; + } + resultArray[pos++] = '-'; + break; + + case '[': + inBrackets = true; + resultArray[pos++] = '['; + + if (i < patternArray.length - 1) { + switch (patternArray[i+1]) { + case '!': + case '^': + resultArray[pos++] = '^'; + i++; + break; + + case ']': + resultArray[pos++] = ']'; + i++; + break; + } + } + break; + + case ']': + resultArray[pos++] = ']'; + inBrackets = false; + break; + + case '\\': + if (i == 0 && patternArray.length > 1 && patternArray[1] == '~') { + resultArray[pos++] = '~'; + ++i; + } else { + resultArray[pos++] = '\\'; + if (i < patternArray.length - 1 && "*?[]".indexOf(patternArray[i+1]) != -1) { //$NON-NLS-1$ + resultArray[pos++] = patternArray[++i]; + } else { + resultArray[pos++] = '\\'; + } + } + break; + + default: + // We must escape characters that are not digits or arrays + // specifically, "^$.{}()+|<>" + if (!Character.isLetterOrDigit(patternArray[i])) { + resultArray[pos++] = '\\'; + } + resultArray[pos++] = patternArray[i]; + break; + } + } + // Must match until the very end + resultArray[pos++] = '$'; + + try { + Pattern pattern = Pattern.compile(new String(resultArray, 0, pos), Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(potentialMatch); + return matcher.find(); + } catch(Exception e) { + // If the user put an invalid pattern, we just ignore it + return false; + } + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java index 848fbafe948..3d33d62131e 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java @@ -102,7 +102,7 @@ public class GdbDebugServicesFactory extends AbstractDsfDebugServicesFactory { return (V)createHardwareAndOSService(session, (ILaunchConfiguration)arg); } } - } + } return super.createService(clazz, session); } @@ -151,7 +151,12 @@ public class GdbDebugServicesFactory extends AbstractDsfDebugServicesFactory { @Override protected IExpressions createExpressionService(DsfSession session) { - return new MIExpressions(session); + // Replace the standard Expressions service with a version that supports pattern matching. + // Pass in the original service which will be used as a delegate. + // This way of doing things allows to keep the pattern matching aspect isolated + // and easy to remove. + IExpressions originialExpressionService = new MIExpressions(session); + return new GDBPatternMatchingExpressions(session, originialExpressionService); } @Override diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.java index a35539cb469..bc3be5a1abd 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.java @@ -20,6 +20,10 @@ class Messages extends NLS { public static String Tracing_not_supported_error; public static String Invalid_post_mortem_type_error; public static String Cannot_get_post_mortem_file_path_error; + public static String GroupPattern; + public static String NoMatches; + public static String UniqueMatch; + public static String UniqueMatches; static { // initialize resource bundle diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.properties b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.properties index ff882bec4ee..8f5360c82ad 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.properties +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/Messages.properties @@ -11,4 +11,8 @@ Tracing_not_supported_error=Tracing not supported Invalid_post_mortem_type_error=Invalid post-mortem type -Cannot_get_post_mortem_file_path_error=Cannot get post mortem file path \ No newline at end of file +Cannot_get_post_mortem_file_path_error=Cannot get post mortem file path +GroupPattern=Group-pattern +NoMatches=No matches +UniqueMatch={0} unique match +UniqueMatches={0} unique matches diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/IGDBPatternMatchingExpressions.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/IGDBPatternMatchingExpressions.java new file mode 100644 index 00000000000..9dcd4fa745a --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/IGDBPatternMatchingExpressions.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2012 Ericsson 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: + * Marc Khouzam (Ericsson) - Initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.dsf.mi.service; + + +/** + * Interface that will indicate that the implementing service supports + * Glob-style expression pattern matching. + * @since 4.2 + */ +public interface IGDBPatternMatchingExpressions extends IMIExpressions { +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIExpressions.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIExpressions.java index 4728a28ff5b..931e85728ce 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIExpressions.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/mi/service/MIExpressions.java @@ -30,6 +30,7 @@ import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.debug.service.ICachingService; import org.eclipse.cdt.dsf.debug.service.IExpressions; import org.eclipse.cdt.dsf.debug.service.IExpressions2; +import org.eclipse.cdt.dsf.debug.service.IExpressions3; import org.eclipse.cdt.dsf.debug.service.IFormattedValues; import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryChangedEvent; import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext; @@ -836,12 +837,18 @@ public class MIExpressions extends AbstractDsfService implements IMIExpressions, // Register to receive service events for this session. getSession().addServiceEventListener(this, null); - // Register this service. - register(new String[] { IExpressions.class.getName(), - IExpressions2.class.getName(), - MIExpressions.class.getName() }, - new Hashtable()); - + // Register this service, but only if we don't already have an + // IExpression service present. This allows another expression + // service to be used, while delegating calls to this service. + if (getServicesTracker().getService(IExpressions.class) == null) { + register(new String[] { IExpressions.class.getName(), + IExpressions2.class.getName(), + IExpressions3.class.getName(), + IMIExpressions.class.getName(), + MIExpressions.class.getName() }, + new Hashtable()); + } + // Create the expressionService-specific CommandControl which is our // variable object manager. // It will deal with the meta-commands, before sending real MI commands