/*******************************************************************************
* Copyright (c) 2000, 2013 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.debug.internal.core;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;
import org.eclipse.cdt.debug.core.CDebugCorePlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.IDynamicVariable;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.IValueVariable;
import org.eclipse.osgi.util.NLS;
/**
* Performs string substitution for context and value variables.
* A clone of {@link org.eclipse.core.internal.variables.StringSubstitutionEngine}.
*/
public class StringSubstitutionEngine {
// Delimiters.
private static final String VARIABLE_START = "${"; //$NON-NLS-1$
private static final char VARIABLE_END = '}';
private static final char VARIABLE_ARG = ':';
// Parsing states.
private static final int SCAN_FOR_START = 0;
private static final int SCAN_FOR_END = 1;
/**
* Resulting string
*/
private StringBuilder fResult;
/**
* Whether substitutions were performed
*/
private boolean fSubs;
/**
* Stack of variables to resolve
*/
private Stack<VariableReference> fStack;
class VariableReference {
// The text inside the variable reference.
private StringBuilder fText;
public VariableReference() {
fText = new StringBuilder();
}
public void append(String text) {
fText.append(text);
}
public String getText() {
return fText.toString();
}
}
/**
* Performs recursive string substitution and returns the resulting string.
*
* @param expression expression to resolve
* @param reportUndefinedVariables whether to report undefined variables as an error
* @param resolveVariables if the variables should be resolved during the substitution
* @param manager registry of variables
* @return the resulting string with all variables recursively substituted
* @exception CoreException if unable to resolve a referenced variable or if a cycle exists
* in referenced variables
*/
public String performStringSubstitution(String expression, boolean reportUndefinedVariables,
boolean resolveVariables, IStringVariableManager manager) throws CoreException {
substitute(expression, reportUndefinedVariables, resolveVariables, manager);
List<HashSet<String>> resolvedVariableSets = new ArrayList<HashSet<String>>();
while (fSubs) {
HashSet<String> resolved = substitute(fResult.toString(), reportUndefinedVariables, true, manager);
for (int i = resolvedVariableSets.size(); --i >= 0;) {
HashSet<String> prevSet = resolvedVariableSets.get(i);
if (prevSet.equals(resolved)) {
HashSet<String> conflictingSet = new HashSet<String>();
for (HashSet<String> set : resolvedVariableSets) {
conflictingSet.addAll(set);
}
StringBuilder problemVariableList = new StringBuilder();
for (String var : conflictingSet) {
problemVariableList.append(var.toString());
problemVariableList.append(", "); //$NON-NLS-1$
}
problemVariableList.setLength(problemVariableList.length() - 2); // Truncate the last ", "
throw new CoreException(new Status(IStatus.ERROR,
CDebugCorePlugin.getUniqueIdentifier(), CDebugCorePlugin.INTERNAL_ERROR,
NLS.bind(InternalDebugCoreMessages.StringSubstitutionEngine_undefined_variable, problemVariableList.toString()), null));
}
}
resolvedVariableSets.add(resolved);
}
return fResult.toString();
}
/**
* Performs recursive string validation to ensure that all of the variables
* contained in the expression exist.
*
* @param expression expression to validate
* @param manager registry of variables
* @exception CoreException if a referenced variable does not exist or if a cycle exists
* in referenced variables
*/
public void validateStringVariables(String expression, IStringVariableManager manager)
throws CoreException {
performStringSubstitution(expression, true, false, manager);
}
/**
* Makes a substitution pass of the given expression returns a Set of the variables that were
* resolved in this pass.
*
* @param expression source expression
* @param reportUndefinedVariables whether to report undefined variables as an error
* @param resolveVariables whether to resolve the value of any variables
* @param manager the {@link IStringVariableManager} to use for the substitution
* @return the set of {@link String}s resolved from the given expression
* @exception CoreException if unable to resolve a variable
*/
private HashSet<String> substitute(String expression, boolean reportUndefinedVariables,
boolean resolveVariables, IStringVariableManager manager) throws CoreException {
fResult = new StringBuilder(expression.length());
fStack = new Stack<VariableReference>();
fSubs = false;
HashSet<String> resolvedVariables = new HashSet<String>();
int pos = 0;
int state = SCAN_FOR_START;
while (pos < expression.length()) {
switch (state) {
case SCAN_FOR_START:
int start = expression.indexOf(VARIABLE_START, pos);
if (start >= 0) {
int length = start - pos;
// Copy non-variable text to the result.
if (length > 0) {
fResult.append(expression.substring(pos, start));
}
pos = start + 2;
state = SCAN_FOR_END;
fStack.push(new VariableReference());
} else {
// Done - no more variables.
fResult.append(expression.substring(pos));
pos = expression.length();
}
break;
case SCAN_FOR_END:
// Be careful of nested variables.
start = expression.indexOf(VARIABLE_START, pos);
int end = expression.indexOf(VARIABLE_END, pos);
if (end < 0) {
// Variables are not completed.
VariableReference tos = fStack.peek();
tos.append(expression.substring(pos));
pos = expression.length();
} else {
if (start >= 0 && start < end) {
// Start of a nested variable.
int length = start - pos;
if (length > 0) {
VariableReference tos = fStack.peek();
tos.append(expression.substring(pos, start));
}
pos = start + 2;
fStack.push(new VariableReference());
} else {
// End of variable reference.
VariableReference tos = fStack.pop();
String substring = expression.substring(pos, end);
tos.append(substring);
resolvedVariables.add(substring);
pos = end + 1;
String value= resolve(tos, reportUndefinedVariables, resolveVariables, manager);
if (value == null) {
value = ""; //$NON-NLS-1$
}
if (fStack.isEmpty()) {
// Append to result.
fResult.append(value);
state = SCAN_FOR_START;
} else {
// Append to previous variable.
tos = fStack.peek();
tos.append(value);
}
}
}
break;
}
}
// Process incomplete variable references.
while (!fStack.isEmpty()) {
VariableReference tos = fStack.pop();
if (fStack.isEmpty()) {
fResult.append(VARIABLE_START);
fResult.append(tos.getText());
} else {
VariableReference var = fStack.peek();
var.append(VARIABLE_START);
var.append(tos.getText());
}
}
return resolvedVariables;
}
/**
* Resolve and return the value of the given variable reference, possibly {@code null}.
*
* @param var the {@link VariableReference} to try and resolve
* @param reportUndefinedVariables whether to report undefined variables as an error
* @param resolveVariables whether to resolve the variables value or just to validate that
* this variable is valid
* @param manager variable registry
* @return variable value, possibly {@code null}
* @exception CoreException if unable to resolve a value
*/
private String resolve(VariableReference var, boolean reportUndefinedVariables,
boolean resolveVariables, IStringVariableManager manager) throws CoreException {
String text = var.getText();
int pos = text.indexOf(VARIABLE_ARG);
String name = null;
String arg = null;
if (pos > 0) {
name = text.substring(0, pos);
pos++;
if (pos < text.length()) {
arg = text.substring(pos);
}
} else {
name = text;
}
IValueVariable valueVariable = manager.getValueVariable(name);
if (valueVariable == null) {
IDynamicVariable dynamicVariable = manager.getDynamicVariable(name);
if (dynamicVariable == null) {
// No variables with the given name.
if (reportUndefinedVariables) {
throw new CoreException(new Status(IStatus.ERROR,
CDebugCorePlugin.getUniqueIdentifier(), CDebugCorePlugin.INTERNAL_ERROR,
NLS.bind(InternalDebugCoreMessages.StringSubstitutionEngine_undefined_variable, name), null));
}
// Leave as is.
return getOriginalVarText(var);
}
if (resolveVariables) {
fSubs = true;
return dynamicVariable.getValue(arg);
}
// Leave as is.
return getOriginalVarText(var);
}
if (arg == null) {
if (resolveVariables) {
fSubs = true;
return valueVariable.getValue();
}
// Leave as is.
return getOriginalVarText(var);
}
// Error - an argument specified for a value variable.
throw new CoreException(new Status(IStatus.ERROR,
CDebugCorePlugin.getUniqueIdentifier(), CDebugCorePlugin.INTERNAL_ERROR,
NLS.bind(InternalDebugCoreMessages.StringSubstitutionEngine_unexpected_argument,
valueVariable.getName()), null));
}
private String getOriginalVarText(VariableReference var) {
StringBuilder res = new StringBuilder(var.getText());
res.insert(0, VARIABLE_START);
res.append(VARIABLE_END);
return res.toString();
}
}