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