| /******************************************************************************* |
| * Copyright (c) 2000, 2006 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 |
| *******************************************************************************/ |
| package org.eclipse.core.internal.variables; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Stack; |
| 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.core.variables.VariablesPlugin; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * Performs string substitution for context and value variables. |
| */ |
| 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 StringBuffer fResult; |
| |
| /** |
| * Whether substitutions were performed |
| */ |
| private boolean fSubs; |
| |
| /** |
| * Stack of variables to resolve |
| */ |
| private Stack fStack; |
| |
| class VariableReference { |
| |
| // the text inside the variable reference |
| private StringBuffer fText; |
| |
| public VariableReference() { |
| fText = new StringBuffer(); |
| } |
| |
| 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 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 resolvedVariableSets = new ArrayList(); |
| while (fSubs) { |
| HashSet resolved = substitute(fResult.toString(), reportUndefinedVariables, true, manager); |
| |
| for(int i=resolvedVariableSets.size()-1; i>=0; i--) { |
| |
| HashSet prevSet = (HashSet)resolvedVariableSets.get(i); |
| |
| if (prevSet.equals(resolved)) { |
| HashSet conflictingSet = new HashSet(); |
| for (; i<resolvedVariableSets.size(); i++) |
| conflictingSet.addAll((HashSet)resolvedVariableSets.get(i)); |
| |
| StringBuffer problemVariableList = new StringBuffer(); |
| for (Iterator it=conflictingSet.iterator(); it.hasNext(); ) { |
| problemVariableList.append(it.next().toString()); |
| problemVariableList.append(", "); //$NON-NLS-1$ |
| } |
| problemVariableList.setLength(problemVariableList.length()-2); //truncate the last ", " |
| throw new CoreException(new Status(IStatus.ERROR, VariablesPlugin.getUniqueIdentifier(), VariablesPlugin.REFERENCE_CYCLE_ERROR, NLS.bind(VariablesMessages.StringSubstitutionEngine_4, new String[]{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 |
| * @exception CoreException if unable to resolve a variable |
| */ |
| private HashSet substitute(String expression, boolean reportUndefinedVariables, boolean resolveVariables, IStringVariableManager manager) throws CoreException { |
| fResult = new StringBuffer(expression.length()); |
| fStack = new Stack(); |
| fSubs = false; |
| |
| HashSet resolvedVariables = new HashSet(); |
| |
| 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 = (VariableReference)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 = (VariableReference)fStack.peek(); |
| tos.append(expression.substring(pos, start)); |
| } |
| pos = start + 2; |
| fStack.push(new VariableReference()); |
| } else { |
| // end of variable reference |
| VariableReference tos = (VariableReference)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 = (VariableReference)fStack.peek(); |
| tos.append(value); |
| } |
| } |
| } |
| break; |
| } |
| } |
| // process incomplete variable references |
| while (!fStack.isEmpty()) { |
| VariableReference tos = (VariableReference)fStack.pop(); |
| if (fStack.isEmpty()) { |
| fResult.append(VARIABLE_START); |
| fResult.append(tos.getText()); |
| } else { |
| VariableReference var = (VariableReference)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</code>. |
| * |
| * @param var |
| * @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</code> |
| * @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, VariablesPlugin.getUniqueIdentifier(), VariablesPlugin.INTERNAL_ERROR, NLS.bind(VariablesMessages.StringSubstitutionEngine_3, new String[]{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, VariablesPlugin.getUniqueIdentifier(), VariablesPlugin.INTERNAL_ERROR, NLS.bind(VariablesMessages.StringSubstitutionEngine_4, new String[]{valueVariable.getName()}), null)); |
| } |
| |
| private String getOriginalVarText(VariableReference var) { |
| StringBuffer res = new StringBuffer(var.getText()); |
| res.insert(0, VARIABLE_START); |
| res.append(VARIABLE_END); |
| return res.toString(); |
| } |
| } |