blob: b5ec1ec381eb60b9857d84f1bb300e92968144f3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2016 Intel Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Intel Corporation - Initial API and implementation
* Andrew Gvozdev (Quoin Inc.)
*******************************************************************************/
package org.eclipse.cdt.utils.cdtvariables;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.cdt.core.cdtvariables.CdtVariableException;
import org.eclipse.cdt.core.cdtvariables.ICdtVariable;
import org.eclipse.cdt.core.cdtvariables.ICdtVariableStatus;
import org.eclipse.cdt.internal.core.cdtvariables.CdtMacroSupplier;
/**
* Utility class to resolve macro and variable references. Provides fixture to parse ${macro}
* expressions and replace macros with actual values using {@link IVariableSubstitutor}.
*
* @since 3.0
*/
public class CdtVariableResolver {
/** @since 5.5 */
public static final String VAR_CONFIG_NAME = CdtMacroSupplier.VAR_CONFIG_NAME;
/** @since 5.5 */
public static final String VAR_CONFIG_DESCRIPTION = CdtMacroSupplier.VAR_CONFIG_DESCRIPTION;
/** @since 5.5 */
public static final String VAR_PROJ_NAME = CdtMacroSupplier.VAR_PROJ_NAME;
/** @since 5.5 */
public static final String VAR_PROJ_DIR_PATH = CdtMacroSupplier.VAR_PROJ_DIR_PATH;
/** @since 5.5 */
public static final String VAR_WORKSPACE_DIR_PATH = CdtMacroSupplier.VAR_WORKSPACE_DIR_PATH;
/** @since 5.5 */
public static final String VAR_DIRECTORY_DELIMITER = CdtMacroSupplier.VAR_DIRECTORY_DELIMITER;
/** @since 5.5 */
public static final String VAR_PATH_DELIMITER = CdtMacroSupplier.VAR_PATH_DELIMITER;
/** @since 5.5 */
public static final String VAR_ECLIPSE_VERSION = CdtMacroSupplier.VAR_ECLIPSE_VERSION;
/** @since 5.5 */
public static final String VAR_CDT_VERSION = CdtMacroSupplier.VAR_CDT_VERSION;
/** @since 5.5 */
public static final String VAR_HOST_OS_NAME = CdtMacroSupplier.VAR_HOST_OS_NAME;
/** @since 5.5 */
public static final String VAR_HOST_ARCH_NAME = CdtMacroSupplier.VAR_HOST_ARCH_NAME;
/** @since 5.5 */
public static final String VAR_OS_TYPE = CdtMacroSupplier.VAR_OS_TYPE;
/** @since 5.5 */
public static final String VAR_ARCH_TYPE = CdtMacroSupplier.VAR_ARCH_TYPE;
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
public static final String VARIABLE_PREFIX = "${"; //$NON-NLS-1$
public static final char VARIABLE_SUFFIX = '}';
private static final String RE_VPREFIX = "\\$\\{"; //$NON-NLS-1$
private static final String RE_VSUFFIX = "\\}"; //$NON-NLS-1$
private static final String RE_VNAME = "[^${}]*"; //$NON-NLS-1$
/**
* Converts list of strings to one string using given string as delimiter,
* i.e -> "string1:string2:string3"
*
* @param value - list of strings to convert.
* @param listDelimiter - delimiter.
* @return all strings from the list separated with given delimiter.
*/
static public String convertStringListToString(String value[], String listDelimiter) {
if (value == null || value.length == 0)
return EMPTY_STRING;
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < value.length; i++) {
buffer.append(value[i]);
if (listDelimiter != null && !EMPTY_STRING.equals(listDelimiter) && i < value.length - 1)
buffer.append(listDelimiter);
}
return buffer.toString();
}
/**
* Resolves macros of kind ${Macro} in the given string by calling the macro substitutor
* for each macro reference found. Macros can be inside one another like
* ${workspace_loc:/${ProjName}/} but resolved just once. No recursive
* macro names are allowed.
* It is not possible to prevent macros from expanding.
* For historical reasons (See Bug 571472), macros that are multi-line according to
* {@link Pattern}'s Line Terminators are not expanded.
*
* @param string - macro expression.
* @param substitutor - macro resolution provider to retrieve macro values.
* @return resolved string
*
* @throws CdtVariableException if substitutor can't handle the macro and returns null or throws.
*/
static public String resolveToString(String string, IVariableSubstitutor substitutor) throws CdtVariableException {
if (string == null) {
return EMPTY_STRING;
}
// Bug 571472 to match historical behaviour, don't substitute multi-line strings
for (char ch : string.toCharArray()) {
if (ch == '\n' || ch == '\r' || (ch | 1) == '\u2029' || ch == '\u0085') {
return string;
}
}
final Pattern pattern = Pattern.compile("(\\$\\{([^${}]*)\\})"); //$NON-NLS-1$
final String VARIABLE_PREFIX_MASKED = "$\1"; //$NON-NLS-1$
final char VARIABLE_SUFFIX_MASKED = '\2';
StringBuilder buffer = new StringBuilder(string);
int limit = string.length();
Matcher matcher = pattern.matcher(buffer);
while (matcher.find()) {
String name = matcher.group(2);
String resolved = name.length() > 0 ? substitutor.resolveToString(name) : EMPTY_STRING;
if (resolved == null) {
throw new CdtVariableException(ICdtVariableStatus.TYPE_MACRO_UNDEFINED, null, string, name);
}
if (limit-- < 0) {
// to prevent incidental endless looping
throw new CdtVariableException(ICdtVariableStatus.TYPE_ERROR, name, string, resolved);
}
// Only one expansion is allowed, so hide any text interfering with macro syntax
resolved = resolved.replace(VARIABLE_PREFIX, VARIABLE_PREFIX_MASKED);
resolved = resolved.replace(VARIABLE_SUFFIX, VARIABLE_SUFFIX_MASKED);
buffer.replace(matcher.start(1), matcher.end(1), resolved);
matcher = pattern.matcher(buffer);
}
String result = buffer.toString();
// take hidden data back
result = result.replace(VARIABLE_PREFIX_MASKED, VARIABLE_PREFIX);
result = result.replace(VARIABLE_SUFFIX_MASKED, VARIABLE_SUFFIX);
return result;
}
/**
* finds the macro references in the given string and calls the macro substitutor for each macro found
* this could be used for obtaining the list of macros referenced in the given string, etc.
*
* @param string
* @param substitutor
* @throws CdtVariableException
*
* @deprecated Use {@link #resolveToString} which would do full nested expansion.
*/
@Deprecated
static public void checkVariables(String string, IVariableSubstitutor substitutor) throws CdtVariableException {
resolveToString(string, substitutor);
}
/**
* Resolves array of macros using {@code substitutor} to pull macro's list of values.
* Note that each macro of input array can in turn provide list of values and
* the resulting array combines all of them.
*
* @param values - input array of macros.
* @param substitutor - macro resolution provider to retrieve macro values.
* @param ignoreErrors - if {@code true} then exceptions are caught and ignored.
* @return array of resolved values.
* @throws CdtVariableException if substitutor throws {@link CdtVariableException}
* and {@code ignoreErrors}={@code null}.
*/
static public String[] resolveStringListValues(String values[], IVariableSubstitutor substitutor,
boolean ignoreErrors) throws CdtVariableException {
String result[] = null;
if (values == null || values.length == 0)
result = values;
else if (values.length == 1)
try {
result = CdtVariableResolver.resolveToStringList(values[0], substitutor);
} catch (CdtVariableException e) {
if (!ignoreErrors)
throw e;
}
else {
List<String> list = new ArrayList<>();
for (String value : values) {
String resolved[];
try {
resolved = CdtVariableResolver.resolveToStringList(value, substitutor);
if (resolved != null && resolved.length > 0)
list.addAll(Arrays.asList(resolved));
} catch (CdtVariableException e) {
if (!ignoreErrors)
throw e;
}
}
result = list.toArray(new String[list.size()]);
}
return result;
}
/**
* Resolves macro ${ListMacro} in the given String to the String-list using substitutor
* to pull macro's list of values. If the provided string is not exactly a single macro
* it is treated as macro expression and result is put into the first element of resulting array.
*
* @param string - input string.
* @param substitutor - macro resolution provider to retrieve macro values.
* @return array of resolved values.
* @throws CdtVariableException if substitutor can't handle the macro and returns null or throws.
*/
static public String[] resolveToStringList(String string, IVariableSubstitutor substitutor)
throws CdtVariableException {
StringBuilder buffer = new StringBuilder(string);
final Pattern pattern = Pattern.compile("^" + RE_VPREFIX + "(" + RE_VNAME + ")" + RE_VSUFFIX + "$"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
Matcher matcher = pattern.matcher(buffer);
if (matcher.matches()) {
String name = matcher.group(1);
if (name.equals(EMPTY_STRING)) {
return new String[0];
}
String[] result = substitutor.resolveToStringList(name);
if (result == null) {
throw new CdtVariableException(ICdtVariableStatus.TYPE_MACRO_UNDEFINED, null, string, name);
}
return result;
}
return new String[] { resolveToString(string, substitutor) };
}
/**
* Test for String-list type of macro.
*
* @param macroType - type of tested macro.
* @return {@code true} if the given macro is a String-list macro.
*/
public static boolean isStringListVariable(int macroType) {
switch (macroType) {
case ICdtVariable.VALUE_TEXT_LIST:
case ICdtVariable.VALUE_PATH_FILE_LIST:
case ICdtVariable.VALUE_PATH_DIR_LIST:
case ICdtVariable.VALUE_PATH_ANY_LIST:
return true;
default:
return false;
}
}
/**
* Checks the macros integrity for the given context. If test fails {@link CdtVariableException}
* is thrown.
*
* @param info - context information to acquire list of available macros.
* @param substitutor - macro resolution provider to retrieve macro values.
* @throws CdtVariableException propagated up if {@code substitutor} throws.
*/
public static void checkIntegrity(IVariableContextInfo info, IVariableSubstitutor substitutor)
throws CdtVariableException {
if (info != null) {
ICdtVariable macros[] = SupplierBasedCdtVariableManager.getVariables(info, true);
if (macros != null) {
for (ICdtVariable macro : macros) {
if (isStringListVariable(macro.getValueType()))
substitutor.resolveToStringList(macro.getName());
else
substitutor.resolveToString(macro.getName());
}
}
}
}
/**
* Constructs a macro reference given the macro name
* e.g. if the "macro1" name is passed, returns "${macro1}"
*
* @param name - macro name.
* @return macro variable in form "${macro}"
*/
public static String createVariableReference(String name) {
return VARIABLE_PREFIX + name + VARIABLE_SUFFIX;
}
}