/*******************************************************************************
* Copyright (c) 2012, 2015 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
* Grzegorz Kuligowski - Cannot cast to type that contain commas (bug 393474)
* Marc Khouzam (Ericsson) - Support for glob-expressions for local variables (bug 394408)
* Alvaro Sanchez-Leon (Ericsson AB) - Allow user to edit register groups (Bug 235747)
*******************************************************************************/
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.IRegisterDMContext;
import org.eclipse.cdt.dsf.debug.service.IRegisters2;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
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.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 expression-groups and glob-pattern matching.
* Expression-groups give the user the ability to create a 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 IMIExpressions, ICachingService {
/**
* A regex representing each character that can be used to separate
* n the different expressions contained in an expression-group.
* 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).
* We also don't allow a comma because they are used in C/C++ templates (bug 393474)
* Furthermore, commas are used within array-index matches as well.
*/
private final static String EXPRESSION_GROUP_SEPARATORS_REGEXP = "[;]"; //$NON-NLS-1$
private final static String REGISTER_PREFIX = "$"; //$NON-NLS-1$
private final static String GLOB_EXPRESSION_PREFIX = "="; //$NON-NLS-1$
/**
* This regular expression describes the supported content of an array index range.
* Valid range formats are are numbers, possibly separated by - and/or ,.
* E.g, "23-56" or "32" or "23, 45-67, 12-15"
*/
private static final String ARRAY_INDEX_RANGE_REGEXP = "^*\\d+(\\s*-\\s*\\d+)?(\\s*,\\s*\\d+(\\s*-\\s*\\d+)?)*$";//$NON-NLS-1$
/**
* An expression-group is an expression that requires expansion into a (potentially empty)
* list of sub-expressions. Using an expression-group allows the user to create groups
* of expressions very quickly.
*
* We support two aspects for expression-goups:
* 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- Separated expressions, each potentially using the glob-syntax
*/
protected static class ExpressionGroupDMC implements IExpressionGroupDMContext {
/**
* The expression context, as created by the main Expression service.
* We delegate the handling of the expression to it.
*/
private IExpressionDMContext fExprDelegate;
public ExpressionGroupDMC(IExpressionDMContext exprDmc) {
fExprDelegate = exprDmc;
}
protected IExpressionDMContext getExprDelegate() {
return fExprDelegate;
}
@Override
public String getExpression() {
return fExprDelegate.getExpression();
}
@Override
public String getSessionId() {
return fExprDelegate.getSessionId();
}
@Override
public IDMContext[] getParents() {
return fExprDelegate.getParents();
};
@Override
public <T> T getAdapter(Class<T> adapterType) {
return fExprDelegate.getAdapter(adapterType);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ExpressionGroupDMC)) return false;
return ((ExpressionGroupDMC)obj).fExprDelegate.equals(fExprDelegate);
}
@Override
public int hashCode() {
return fExprDelegate.hashCode();
}
@Override
public String toString() {
return "Group: " + getExprDelegate().toString(); //$NON-NLS-1$
}
}
/**
* The model data interface for expression-groups
*/
protected static class ExpressionGroupDMData implements IExpressionDMDataExtension {
private final String fRelativeExpression;
private final int fNumChildren;
public ExpressionGroupDMData(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<String, Integer> getEnumerations() {
return new HashMap<String, Integer>();
}
@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 ExpressionGroupDMData)) return false;
return fRelativeExpression.equals(((ExpressionGroupDMData)other).fRelativeExpression);
}
@Override
public int hashCode() {
return fRelativeExpression.hashCode();
}
@Override
public String toString() {
return "ExprGroup: " + fRelativeExpression; //$NON-NLS-1$
}
}
/**
* The base expression service to which we delegate all non-expression-group logic.
*/
private IMIExpressions fDelegate;
public GDBPatternMatchingExpressions(DsfSession session, IMIExpressions delegate) {
super(session);
fDelegate = 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<String, String>());
// 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 (isExpressionGroup(expression)) {
return new ExpressionGroupDMC(expressionDmc);
} else {
return expressionDmc;
}
}
@Override
public ICastedExpressionDMContext createCastedExpression(IExpressionDMContext context, CastInfo castInfo) {
// Cannot cast an expression-group
assert (!(context instanceof IExpressionGroupDMContext));
return fDelegate.createCastedExpression(context, castInfo);
}
@Override
public void getExpressionDataExtension(final IExpressionDMContext dmc, final DataRequestMonitor<IExpressionDMDataExtension> rm) {
if (dmc instanceof IExpressionGroupDMContext) {
getSubExpressionCount(dmc, new ImmediateDataRequestMonitor<Integer>(rm) {
@Override
protected void handleSuccess() {
rm.done(new ExpressionGroupDMData(((IExpressionGroupDMContext)dmc).getExpression(), getData()));
}
});
return;
}
fDelegate.getExpressionDataExtension(dmc, rm);
}
@Override
public void getExpressionData(final IExpressionDMContext dmc, final DataRequestMonitor<IExpressionDMData> rm) {
if (dmc instanceof IExpressionGroupDMContext) {
getSubExpressionCount(dmc, new ImmediateDataRequestMonitor<Integer>(rm) {
@Override
protected void handleSuccess() {
rm.done(new ExpressionGroupDMData(((IExpressionGroupDMContext)dmc).getExpression(), getData()));
}
});
return;
}
fDelegate.getExpressionData(dmc, rm);
}
@Override
public void getExpressionAddressData(IExpressionDMContext dmc, DataRequestMonitor<IExpressionDMAddress> rm) {
// An expression-group does not have an address
if (dmc instanceof IExpressionGroupDMContext) {
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<IExpressionDMContext[]> rm) {
if (exprCtx instanceof IExpressionGroupDMContext) {
matchExpressionGroup((IExpressionGroupDMContext)exprCtx, -1, -1, rm);
} else {
fDelegate.getSubExpressions(exprCtx, rm);
}
}
@Override
public void getSubExpressions(IExpressionDMContext exprCtx, int startIndex, int length, DataRequestMonitor<IExpressionDMContext[]> rm) {
if (exprCtx instanceof IExpressionGroupDMContext) {
matchExpressionGroup((IExpressionGroupDMContext)exprCtx, startIndex, length, rm);
} else {
fDelegate.getSubExpressions(exprCtx, startIndex, length, rm);
}
}
@Override
public void getSubExpressionCount(IExpressionDMContext dmc, final DataRequestMonitor<Integer> rm) {
if (dmc instanceof IExpressionGroupDMContext) {
matchExpressionGroup((IExpressionGroupDMContext)dmc, -1, -1, new ImmediateDataRequestMonitor<IExpressionDMContext[]>(rm) {
@Override
protected void handleSuccess() {
rm.done(getData().length);
}
});
} else {
fDelegate.getSubExpressionCount(dmc, rm);
}
}
@Override
public void getSubExpressionCount(IExpressionDMContext dmc, int maxNumberOfChildren, final DataRequestMonitor<Integer> rm) {
if (dmc instanceof IExpressionGroupDMContext) {
// No need to worry about maxNumberOfChildren for the case of an expression-group, since there won't be
// a very large amount of them.
matchExpressionGroup((IExpressionGroupDMContext)dmc, -1, -1, new ImmediateDataRequestMonitor<IExpressionDMContext[]>(rm) {
@Override
protected void handleSuccess() {
rm.done(getData().length);
}
});
} else {
fDelegate.getSubExpressionCount(dmc, maxNumberOfChildren, rm);
}
}
@Override
public void getBaseExpressions(IExpressionDMContext exprContext, DataRequestMonitor<IExpressionDMContext[]> 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<Boolean> rm) {
// An expression-group's value cannot be modified
if (dmc instanceof IExpressionGroupDMContext) {
rm.done(false);
return;
}
fDelegate.canWriteExpression(dmc, rm);
}
@Override
public void writeExpression(IExpressionDMContext dmc, String expressionValue, String formatId, RequestMonitor rm) {
// An expression-group's value cannot be modified
assert !(dmc instanceof IExpressionGroupDMContext);
fDelegate.writeExpression(dmc, expressionValue, formatId, rm);
}
@Override
public void getAvailableFormats(IFormattedDataDMContext dmc, DataRequestMonitor<String[]> rm) {
// For an expression-group, we only show the NATURAL format
if (dmc instanceof IExpressionGroupDMContext) {
rm.done(new String[] { IFormattedValues.NATURAL_FORMAT });
return;
}
fDelegate.getAvailableFormats(dmc, rm);
}
@Override
public FormattedValueDMContext getFormattedValueContext(IFormattedDataDMContext dmc, String formatId) {
// No special handling for expression-groups
return fDelegate.getFormattedValueContext(dmc, formatId);
}
@Override
public void getFormattedExpressionValue(FormattedValueDMContext dmc, final DataRequestMonitor<FormattedValueDMData> rm) {
IExpressionGroupDMContext exprGroup = DMContexts.getAncestorOfType(dmc, IExpressionGroupDMContext.class);
if (exprGroup != null) {
getSubExpressionCount(exprGroup, new ImmediateDataRequestMonitor<Integer>(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<Boolean> rm) {
// Always safe to ask for all sub-expression of an expression-group,
// since we don't expect large amounts of children
if (dmc instanceof IExpressionGroupDMContext) {
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 an expression-group.
* @param expr The expression to verify
* @return True if expr is an expression-group. An
* expression-group is either a separated list of
* expressions, or an expression using a glob-pattern
*/
protected boolean isExpressionGroup(String expr) {
// First check for a 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 add any character before
// and after the regex, which achieves what we want.
// Note that checking if (expr.split(regex) > 1), will not notice
// an expression that has a separator only at the end.
if (expr.matches(".*" + EXPRESSION_GROUP_SEPARATORS_REGEXP +".*")) { //$NON-NLS-1$ //$NON-NLS-2$
// We are dealing with a group of expressions.
// It may not be a valid one, but it is one nonetheless.
return true;
}
// Not a list. Check if we are dealing with a glob-pattern.
return isGlobExpression(expr);
}
/**
* Verify if we are dealing with a glob-pattern.
* We support the expression '*' which will match all local variables
* as well as the expression '$*' which will match all registers.
* We support glob-patterns for any expression starting with '='
*
* @param expr The expression to verify
* @return True if expr is a glob-pattern we support.
*/
protected boolean isGlobExpression(String expr) {
// Get rid of useless whitespace
expr = expr.trim();
// We support the glob-pattern '*' to indicate all local variables
// and $* for all registers
if (expr.equals("*") || expr.equals("$*")) { //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
// Glob-expressions must start with '='
if (expr.startsWith(GLOB_EXPRESSION_PREFIX)) {
return true;
}
return false;
}
/**
* Verify if the glob-pattern represents a register.
*
* @param expr The glob-pattern that may be a register-pattern
* @return True if expr follows the rules of an register-pattern
*/
protected boolean isRegisterPattern(String expr) {
// Get rid of useless whitespace
expr = expr.trim();
if (expr.startsWith(REGISTER_PREFIX)) {
return true;
}
return false;
}
/**
* Verify if the glob-pattern should be handled as an array index range.
* When dealing with variables (on contrast to registers), the [] will
* map to array indices instead of ranges within the array name.
* For example =array[1-2] will map to array[1] and array[2] instead of
* array1 and array2.
*
* If the range contains non-digits, the matching will not be handled
* as array indices.
*
* @param expr The glob-pattern that may be an array-pattern
* @return True if expr follows the rules of an array-pattern
*/
protected boolean isArrayPattern(String expr) {
// Get rid of useless whitespace
expr = expr.trim();
int openBracketIndex = expr.indexOf('[');
// There must be an open bracket and it cannot be in the first position
// (as we need some indication of the array name before the brackets)
if (openBracketIndex < 1) {
return false;
}
// We don't support any characters after the closing bracket
// since we don't support any operations on an expression-group.
if (!expr.endsWith("]")) { //$NON-NLS-1$
return false;
}
// We have a match for a variable which uses brackets.
// Check if the indices are integer ranges (using - or commas).
try {
Pattern pattern = Pattern.compile(ARRAY_INDEX_RANGE_REGEXP, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(expr.substring(openBracketIndex+1, expr.length()-1));
if (!matcher.find()) {
return false;
}
} catch(Exception e) {
// If the user put an invalid pattern, we just ignore it
return false;
}
return true;
}
/**
* Split the expression-group into a list of individual expression strings.
*/
protected List<String> splitExpressionsInGroup(IExpressionGroupDMContext groupDmc) {
// Split the list of expressions
String[] splitExpressions = groupDmc.getExpression().split(EXPRESSION_GROUP_SEPARATORS_REGEXP);
// Remove any extra whitespace from each resulting expression,
// and ignore any empty expressions.
List<String> expressions = new ArrayList<String>(splitExpressions.length);
for (String expr : splitExpressions) {
expr = expr.trim();
if (!expr.isEmpty()) {
expressions.add(expr);
}
}
return expressions;
}
/**
* Find all expressions that match the specified expression-group.
* This method retains the order of the expressions in the expression-group, to show them
* in the same order as the one specified by the user. The matches of each expression within the group
* are sorted alphabetically however.
*
* @param exprGroupDmc The expression-group 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 matchExpressionGroup(final IExpressionGroupDMContext exprGroupDmc, int startIndex, int length,
final DataRequestMonitor<IExpressionDMContext[]> 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<String> exprList = new ArrayList<String>(splitExpressionsInGroup(exprGroupDmc));
// List to store the final result, which is all the sub-expressions of this group
final ArrayList<IExpressionDMContext> subExprList = new ArrayList<IExpressionDMContext>();
final int startIndex1 = (startIndex < 0) ? 0 : startIndex;
final int length1 = (length < 0) ? Integer.MAX_VALUE : length;
matchExpressionList(exprList, subExprList, exprGroupDmc, 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<IExpressionDMContext> uniqueSubExprSet = new LinkedHashSet<IExpressionDMContext>(subExprList);
subExprList.clear();
subExprList.addAll(uniqueSubExprSet);
// Extract the range of interest from the final list
int endIndex = Math.min(startIndex1 + length1, subExprList.size());
List<IExpressionDMContext> 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<String> exprList, final List<IExpressionDMContext> 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);
IExpressionDMContext exprDmc = createExpression(parentDmc, expr);
if (exprDmc instanceof IExpressionGroupDMContext) {
matchGlobExpression((IExpressionGroupDMContext)exprDmc, new ImmediateDataRequestMonitor<List<IExpressionDMContext>>(rm) {
@Override
protected void handleSuccess() {
List<IExpressionDMContext> matches = getData();
// Sort the matches to be more user-friendly
Collections.sort(matches, new Comparator<IExpressionDMContext>() {
@Override
public int compare(IExpressionDMContext o1, IExpressionDMContext o2) {
// For elements of the same array, we need to sort by index
if (isArrayPattern(o1.getExpression()) && isArrayPattern(o2.getExpression())) {
// Extract the array names and the array indices specification.
// The regex used will remove both [ and ]
String[] arrayExprParts1 = o1.getExpression().split("[\\[\\]]"); //$NON-NLS-1$
assert arrayExprParts1 != null && arrayExprParts1.length == 2;
String[] arrayExprParts2 = o2.getExpression().split("[\\[\\]]"); //$NON-NLS-1$
assert arrayExprParts2 != null && arrayExprParts2.length == 2;
// Compare array names
if (arrayExprParts1[0].compareTo(arrayExprParts2[0]) == 0) {
// We are dealing with the same array
try {
int arrayIndex1 = Integer.parseInt(arrayExprParts1[1]);
int arrayIndex2 = Integer.parseInt(arrayExprParts2[1]);
if (arrayIndex1 == arrayIndex2) return 0;
if (arrayIndex1 > arrayIndex2) return 1;
return -1;
} catch (NumberFormatException e) {
// Invalid array index. Fall-back to sorting lexically.
}
}
}
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(exprDmc);
// 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 unsorted matches.
*/
protected void matchGlobExpression(final IExpressionGroupDMContext exprDmc, final DataRequestMonitor<List<IExpressionDMContext>> rm) {
String fullExpr = exprDmc.getExpression().trim();
if (fullExpr.startsWith(GLOB_EXPRESSION_PREFIX)) {
// Strip the leading '=' and any extra spaces
fullExpr = fullExpr.substring(1).trim();
}
if (isRegisterPattern(fullExpr)) {
matchRegisters(exprDmc, rm);
} else {
if (!isArrayPattern(fullExpr)) {
matchLocals(exprDmc, rm);
} else {
// If we are dealing with an expression that could represent an array, we must
// try to match arrays and non-arrays. The reason is that a pattern such as
// =a[1-2] can be a valid match for both a[1], a[2] and a1, a2.
matchArrays(exprDmc, new ImmediateDataRequestMonitor<List<IExpressionDMContext>>(rm) {
@Override
protected void handleSuccess() {
final List<IExpressionDMContext> exprList =
getData() != null ? getData() : new ArrayList<IExpressions.IExpressionDMContext>();
matchLocals(exprDmc, new ImmediateDataRequestMonitor<List<IExpressionDMContext>>(rm) {
@Override
protected void handleSuccess() {
if (getData() != null) {
exprList.addAll(getData());
}
rm.done(exprList);
}
});
}
});
}
}
}
/**
* Find all registers that match the specified glob-pattern.
*
* @param globDmc The glob-expression context for which we want the matches (sub-expressions)
* @param rm RequestMonitor that will contain the unsorted matches.
*/
protected void matchRegisters(final IExpressionGroupDMContext globDmc, final DataRequestMonitor<List<IExpressionDMContext>> rm) {
final IRegisters2 registerService = getServicesTracker().getService(IRegisters2.class);
if (registerService == null) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Register service unavailable", null)); //$NON-NLS-1$
return;
}
final IContainerDMContext contDmc = DMContexts.getAncestorOfType(globDmc, IContainerDMContext.class);
registerService.getRegisters(
new CompositeDMContext(new IDMContext[] { contDmc, globDmc } ),
new ImmediateDataRequestMonitor<IRegisterDMContext[]>(rm) {
@Override
protected void handleSuccess() {
assert getData() instanceof MIRegisterDMC[];
ArrayList<IExpressionDMContext> matches = new ArrayList<IExpressionDMContext>();
String fullExpr = globDmc.getExpression().trim();
if (fullExpr.startsWith(GLOB_EXPRESSION_PREFIX)) {
// Strip the leading '=' and any extra spaces
fullExpr = fullExpr.substring(1).trim();
}
for (MIRegisterDMC register : (MIRegisterDMC[])getData()) {
String potentialMatch = REGISTER_PREFIX + register.getName();
if (globMatches(fullExpr, potentialMatch)) {
matches.add(createExpression(globDmc, potentialMatch));
}
}
rm.done(matches);
}
});
}
/**
* Find all local variables that match the specified glob-pattern.
*
* @param globDmc The glob-expression context for which we want the matches (sub-expressions)
* @param rm RequestMonitor that will contain the unsorted matches.
*/
protected void matchLocals(final IExpressionGroupDMContext globDmc, final DataRequestMonitor<List<IExpressionDMContext>> rm) {
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<IVariableDMContext[]>(rm) {
@Override
protected void handleSuccess() {
IVariableDMContext[] localsDMCs = getData();
final IVariableDMData[] localsDMData = new IVariableDMData[localsDMCs.length];
final CountingRequestMonitor varNameCRM = new CountingRequestMonitor(getExecutor(), rm) {
@Override
public void handleSuccess() {
ArrayList<IExpressionDMContext> matches = new ArrayList<IExpressionDMContext>(localsDMData.length);
String fullExpr = globDmc.getExpression().trim();
if (fullExpr.startsWith(GLOB_EXPRESSION_PREFIX)) {
// Strip the leading '=' and any extra spaces
fullExpr = fullExpr.substring(1).trim();
}
for (IVariableDMData localDMData : localsDMData) {
String potentialMatch = localDMData.getName();
if (globMatches(fullExpr, potentialMatch)) {
matches.add(createExpression(globDmc, potentialMatch));
}
}
rm.done(matches);
}
};
// Get all the names of the variables
int count = 0;
for (int index=0; index < localsDMCs.length; index++) {
final int finalIndex = index;
stackService.getVariableData(localsDMCs[finalIndex], new ImmediateDataRequestMonitor<IVariableDMData>(varNameCRM) {
@Override
public void handleSuccess() {
localsDMData[finalIndex] = getData();
varNameCRM.done();
}
});
count++;
}
varNameCRM.setDoneCount(count);
}
});
}
/**
* Find all arrays elements that match the specified glob-pattern.
*
* @param globDmc The glob-expression context for which we want the matches (sub-expressions)
* @param rm RequestMonitor that will contain the unsorted matches.
*/
protected void matchArrays(final IExpressionGroupDMContext globDmc, final DataRequestMonitor<List<IExpressionDMContext>> rm) {
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;
}
String fullExpr = globDmc.getExpression().trim();
if (fullExpr.startsWith(GLOB_EXPRESSION_PREFIX)) {
// Strip the leading '=' and any extra spaces
fullExpr = fullExpr.substring(1).trim();
}
// Extract the array name and the array index specification.
// The regex used will remove both [ and ]
String[] arrayExprParts = fullExpr.split("[\\[\\]]"); //$NON-NLS-1$
assert arrayExprParts != null && arrayExprParts.length == 2;
if (arrayExprParts == null || arrayExprParts.length < 2) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Error parsing array expression", null)); //$NON-NLS-1$
return;
}
final String arrayName = arrayExprParts[0].trim();
final String arrayIndexSpec = arrayExprParts[1].trim();
stackService.getLocals(frameCtx, new ImmediateDataRequestMonitor<IVariableDMContext[]>(rm) {
@Override
protected void handleSuccess() {
IVariableDMContext[] localsDMCs = getData();
final IVariableDMData[] localsDMData = new IVariableDMData[localsDMCs.length];
final CountingRequestMonitor varNameCRM = new CountingRequestMonitor(getExecutor(), rm) {
@Override
public void handleSuccess() {
final ArrayList<IExpressionDMContext> matches = new ArrayList<IExpressionDMContext>();
final CountingRequestMonitor elementMatchesCRM = new CountingRequestMonitor(getExecutor(), rm) {
@Override
public void handleSuccess() {
rm.done(matches);
}
};
int count = 0;
for (IVariableDMData localDMData : localsDMData) {
final String potentialMatch = localDMData.getName();
if (globMatches(arrayName, potentialMatch)) {
// We have a variable that matches the name part of the array.
// Let's create the matching elements if that variable is an array.
createPotentialArrayMatches(createExpression(globDmc, potentialMatch), arrayIndexSpec,
new ImmediateDataRequestMonitor<List<IExpressionDMContext>>(elementMatchesCRM){
@Override
protected void handleSuccess() {
if (getData() != null) {
matches.addAll(getData());
}
elementMatchesCRM.done();
}
});
count++;
}
}
elementMatchesCRM.setDoneCount(count);
}
};
// Get all the names of the variables
int count = 0;
for (int index=0; index < localsDMCs.length; index++) {
final int finalIndex = index;
stackService.getVariableData(localsDMCs[finalIndex], new ImmediateDataRequestMonitor<IVariableDMData>(varNameCRM) {
@Override
public void handleSuccess() {
localsDMData[finalIndex] = getData();
varNameCRM.done();
}
});
count++;
}
varNameCRM.setDoneCount(count);
}
});
}
/**
* Creates requested array elements if exprDmc is indeed an array.
*
* @param exprDmc The potential array expression to be used
* @param indexSpec The specification of the element indices
* @param rm The list of created element expressions.
* If exprDmc is not an array, the list will be empty but not null.
*/
protected void createPotentialArrayMatches(final IExpressionDMContext exprDmc, final String indexSpec,
final DataRequestMonitor<List<IExpressionDMContext>> rm) {
// We check if the variable is an array or not. If it is an array,
// we create the elements based on the specified indices.
// If it is not an array, we don't need to handle it in this method
getExpressionData(exprDmc, new ImmediateDataRequestMonitor<IExpressionDMData>(rm) {
@Override
protected void handleCompleted() {
boolean isArray =
isSuccess() &&
getData().getBasicType().equals(IExpressionDMData.BasicType.array);
final ArrayList<IExpressionDMContext> elements = new ArrayList<IExpressionDMContext>();
if (isArray) {
// we must now create the elements based on the indices
List<IExpressionDMContext> indicesDmcs =
createArrayIndicesExpression(exprDmc, indexSpec);
if (indicesDmcs != null) {
elements.addAll(indicesDmcs);
}
}
rm.done(elements);
}
});
}
/**
* Create all the expressions characterizing the specified arrayDmc and
* indexSpec pattern.
*
* @param arrayDmc The expression context that represents the array itself
* @param indexSpec A string describing the range of indexes to be used.
* Valid range formats are described by {@code ARRAY_INDEX_RANGE_REGEXP}
* The string should not contain the index [] characters.
* @return A list of expression contexts representing all the different
* array elements possible using the name of the array and indexSpec.
* If the indexSpec is invalid (e.g, 3-2) it will be used as-is which
* could be a valid expression (i.e., the index 3-2=1 in this case)
*/
protected List<IExpressionDMContext> createArrayIndicesExpression(IExpressionDMContext arrayDmc, String indexSpec) {
ArrayList<IExpressionDMContext> expressionDMCs = new ArrayList<IExpressionDMContext>();
String arrayName = arrayDmc.getExpression();
IDMContext parentDmc = arrayDmc.getParents()[0];
// First split the indexRange by comma
String[] ranges = indexSpec.split(","); //$NON-NLS-1$
for (String range : ranges) {
// Get rid of any useless spaces
range = range.trim();
// Try to split the range with the - separator
String[] rangeNumbers = range.split("-");//$NON-NLS-1$
if (rangeNumbers.length == 2) {
try {
int lowIndex = Integer.parseInt(rangeNumbers[0]);
int highIndex = Integer.parseInt(rangeNumbers[1]);
if (lowIndex <= highIndex) {
for (int i = lowIndex; i <= highIndex; i++) {
expressionDMCs.add(createExpression(parentDmc, arrayName + "[" + i + "]")); //$NON-NLS-1$ //$NON-NLS-2$
}
continue;
}
} catch (NumberFormatException e) {
// Ignore and fall back on using range as-is below
}
}
// Leave range as-is, which could be a single digit, or some non-expected expression
expressionDMCs.add(createExpression(parentDmc, arrayName + "[" + range + "]")); //$NON-NLS-1$ //$NON-NLS-2$
}
return expressionDMCs;
}
/**
* 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 if 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;
}
}
}