blob: 013dc3ee44177a448cde41fb596b5d5b32c24299 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2016 Freescale Semiconductor 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:
* Serge Beauchamp (Freescale Semiconductor) - initial API and implementation
* James Blackburn (Broadcom Corp.) - ongoing development
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
* Mickael Istria (Red Hat Inc.) - Bug 488937
*******************************************************************************/
package org.eclipse.core.internal.resources;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedList;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.internal.resources.projectvariables.*;
import org.eclipse.core.resources.IPathVariableManager;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
public class PathVariableUtil {
static public String getUniqueVariableName(String variable, IResource resource) {
int index = 1;
variable = getValidVariableName(variable);
String destVariable = variable;
IPathVariableManager pathVariableManager = resource.getPathVariableManager();
if (destVariable.startsWith(ParentVariableResolver.NAME) || destVariable.startsWith(ProjectLocationVariableResolver.NAME))
destVariable = "copy_" + destVariable; //$NON-NLS-1$
while (pathVariableManager.isDefined(destVariable)) {
destVariable = destVariable + index;
index++;
}
return destVariable;
}
public static String getValidVariableName(String variable) {
// remove the argument part if the variable is of the form ${VAR-ARG}
int argumentIndex = variable.indexOf('-');
if (argumentIndex != -1)
variable = variable.substring(0, argumentIndex);
variable = variable.trim();
char first = variable.charAt(0);
if (!Character.isLetter(first) && first != '_') {
variable = 'A' + variable;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < variable.length(); i++) {
char c = variable.charAt(i);
if ((Character.isLetter(c) || Character.isDigit(c) || c == '_') && !Character.isWhitespace(c))
builder.append(c);
}
variable = builder.toString();
return variable;
}
public static IPath convertToPathRelativeMacro(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException {
return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, true);
}
static public IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException {
return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, false);
}
static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint) throws CoreException {
return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint, true, false));
}
static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException {
return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint));
}
static private IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException {
if (variableHint != null && pathVariableManager.isDefined(variableHint)) {
IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint));
if (value != null)
return wrapInProperFormat(makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variableHint, generateMacro), generateMacro);
}
IPath path = convertToProperCase(originalPath);
IPath newPath = null;
int maxMatchLength = -1;
String[] existingVariables = pathVariableManager.getPathVariableNames();
for (int i = 0; i < existingVariables.length; i++) {
String variable = existingVariables[i];
if (skipWorkspace) {
// Variables relative to the workspace are not portable, and defeat the purpose of having linked resource locations,
// so they should not automatically be created relative to the workspace.
if (variable.equals(WorkspaceLocationVariableResolver.NAME))
continue;
}
if (variable.equals(WorkspaceParentLocationVariableResolver.NAME))
continue;
if (variable.equals(ParentVariableResolver.NAME))
continue;
// find closest path to the original path
IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable));
if (value != null) {
value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value))));
if (value.isPrefixOf(path)) {
int matchLength = value.segmentCount();
if (matchLength > maxMatchLength) {
maxMatchLength = matchLength;
newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro);
}
}
}
}
if (newPath != null)
return wrapInProperFormat(newPath, generateMacro);
if (force) {
int originalSegmentCount = originalPath.segmentCount();
for (int j = 0; j <= originalSegmentCount; j++) {
IPath matchingPath = path.removeLastSegments(j);
int minDifference = Integer.MAX_VALUE;
for (int k = 0; k < existingVariables.length; k++) {
String variable = existingVariables[k];
if (skipWorkspace) {
if (variable.equals(WorkspaceLocationVariableResolver.NAME))
continue;
}
if (variable.equals(WorkspaceParentLocationVariableResolver.NAME))
continue;
if (variable.equals(ParentVariableResolver.NAME))
continue;
IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable));
if (value != null) {
value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value))));
if (matchingPath.isPrefixOf(value)) {
int difference = value.segmentCount() - originalSegmentCount;
if (difference < minDifference) {
minDifference = difference;
newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro);
}
}
}
}
if (newPath != null)
return wrapInProperFormat(newPath, generateMacro);
}
if (originalSegmentCount == 0) {
String variable = ProjectLocationVariableResolver.NAME;
IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable));
value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value))));
if (originalPath.isPrefixOf(value))
newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro);
if (newPath != null)
return wrapInProperFormat(newPath, generateMacro);
}
}
if (skipWorkspace)
return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, false, generateMacro);
return originalPath;
}
private static IPath wrapInProperFormat(IPath newPath, boolean generateMacro) {
if (generateMacro)
newPath = PathVariableUtil.buildVariableMacro(newPath);
return newPath;
}
private static IPath makeRelativeToVariable(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean generateMacro) {
IPath path = convertToProperCase(originalPath);
IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint));
value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value))));
int valueSegmentCount = value.segmentCount();
if (value.isPrefixOf(path)) {
// transform "c:/foo/bar" into "FOO/bar"
IPath tmp = Path.fromOSString(variableHint);
for (int j = valueSegmentCount; j < originalPath.segmentCount(); j++) {
tmp = tmp.append(originalPath.segment(j));
}
return tmp;
}
if (force) {
if (devicesAreCompatible(path, value)) {
// transform "c:/foo/bar/other_child/file.txt" into "${PARENT-1-BAR_CHILD}/other_child/file.txt"
int matchingFirstSegments = path.matchingFirstSegments(value);
if (matchingFirstSegments >= 0) {
String originalName = buildParentPathVariable(variableHint, valueSegmentCount - matchingFirstSegments, true);
IPath tmp = Path.fromOSString(originalName);
for (int j = matchingFirstSegments; j < originalPath.segmentCount(); j++) {
tmp = tmp.append(originalPath.segment(j));
}
return tmp;
}
}
}
return originalPath;
}
private static boolean devicesAreCompatible(IPath path, IPath value) {
return (path.getDevice() != null && value.getDevice() != null) ? (path.getDevice().equals(value.getDevice())) : (path.getDevice() == value.getDevice());
}
static private IPath convertToProperCase(IPath path) {
if (Platform.getOS().equals(Platform.OS_WIN32))
return Path.fromPortableString(path.toPortableString().toLowerCase());
return path;
}
static public boolean isParentVariable(String variableString) {
return variableString.startsWith(ParentVariableResolver.NAME + '-');
}
// the format is PARENT-COUNT-ARGUMENT
static public int getParentVariableCount(String variableString) {
String items[] = variableString.split("-"); //$NON-NLS-1$
if (items.length == 3) {
try {
Integer count = Integer.valueOf(items[1]);
return count.intValue();
} catch (NumberFormatException e) {
// nothing
}
}
return -1;
}
// the format is PARENT-COUNT-ARGUMENT
static public String getParentVariableArgument(String variableString) {
String items[] = variableString.split("-"); //$NON-NLS-1$
if (items.length == 3)
return items[2];
return null;
}
static public String buildParentPathVariable(String variable, int difference, boolean generateMacro) {
String newString = ParentVariableResolver.NAME + "-" + difference + "-" + variable; //$NON-NLS-1$//$NON-NLS-2$
if (!generateMacro)
newString = "${" + newString + "}"; //$NON-NLS-1$//$NON-NLS-2$
return newString;
}
public static IPath buildVariableMacro(IPath relativeSrcValue) {
String variable = relativeSrcValue.segment(0);
variable = "${" + variable + "}"; //$NON-NLS-1$//$NON-NLS-2$
return Path.fromOSString(variable).append(relativeSrcValue.removeFirstSegments(1));
}
public static String convertFromUserEditableFormatInternal(IPathVariableManager manager, String userFormat, boolean locationFormat) {
char pathPrefix = 0;
if ((userFormat.length() > 0) && (userFormat.charAt(0) == '/' || userFormat.charAt(0) == '\\'))
pathPrefix = userFormat.charAt(0);
String components[] = splitPathComponents(userFormat);
for (int i = 0; i < components.length; i++) {
if (components[i] == null)
continue;
if (isDotDot(components[i])) {
int parentCount = 1;
components[i] = null;
for (int j = i + 1; j < components.length; j++) {
if (components[j] != null) {
if (isDotDot(components[j])) {
parentCount++;
components[j] = null;
} else
break;
}
}
if (i == 0) // this means the value is implicitly relative to the project location
components[0] = PathVariableUtil.buildParentPathVariable(ProjectLocationVariableResolver.NAME, parentCount, false);
else {
for (int j = i - 1; j >= 0; j--) {
if (parentCount == 0)
break;
if (components[j] == null)
continue;
String variable = extractVariable(components[j]);
boolean hasVariableWithMacroSyntax = true;
if (variable.length() == 0 && (locationFormat && j == 0)) {
variable = components[j];
hasVariableWithMacroSyntax = false;
}
try {
if (variable.length() > 0) {
String prefix = ""; //$NON-NLS-1$
if (hasVariableWithMacroSyntax) {
int indexOfVariable = components[j].indexOf(variable) - "${".length(); //$NON-NLS-1$
prefix = components[j].substring(0, indexOfVariable);
String suffix = components[j].substring(indexOfVariable + "${".length() + variable.length() + "}".length()); //$NON-NLS-1$ //$NON-NLS-2$
if (suffix.length() != 0) {
// Create an intermediate variable, since a syntax of "${VAR}foo/../"
// can't be converted to a "${PARENT-1-VAR}foo" variable.
// So instead, an intermediate variable "VARFOO" will be created of value
// "${VAR}foo", and the string "${PARENT-1-VARFOO}" will be inserted.
String intermediateVariable = PathVariableUtil.getValidVariableName(variable + suffix);
IPath intermediateValue = Path.fromPortableString(components[j]);
int intermediateVariableIndex = 1;
String originalIntermediateVariableName = intermediateVariable;
while (manager.isDefined(intermediateVariable)) {
IPath tmpValue = URIUtil.toPath(manager.getURIValue(intermediateVariable));
if (tmpValue.equals(intermediateValue))
break;
intermediateVariable = originalIntermediateVariableName + intermediateVariableIndex;
}
if (!manager.isDefined(intermediateVariable))
manager.setURIValue(intermediateVariable, URIUtil.toURI(intermediateValue));
variable = intermediateVariable;
prefix = ""; //$NON-NLS-1$
}
}
String newVariable = variable;
if (PathVariableUtil.isParentVariable(variable)) {
String argument = PathVariableUtil.getParentVariableArgument(variable);
int count = PathVariableUtil.getParentVariableCount(variable);
if (argument != null && count != -1)
newVariable = PathVariableUtil.buildParentPathVariable(argument, count + parentCount, locationFormat);
else
newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat);
} else
newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat);
components[j] = prefix + newVariable;
break;
}
components[j] = null;
parentCount--;
} catch (CoreException e) {
components[j] = null;
parentCount--;
}
}
}
}
}
StringBuilder buffer = new StringBuilder();
if (pathPrefix != 0)
buffer.append(pathPrefix);
for (int i = 0; i < components.length; i++) {
if (components[i] != null) {
if (i > 0)
buffer.append(java.io.File.separator);
buffer.append(components[i]);
}
}
return buffer.toString();
}
private static boolean isDotDot(String component) {
return component.equals(".."); //$NON-NLS-1$
}
private static String[] splitPathComponents(String userFormat) {
ArrayList<String> list = new ArrayList<>();
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < userFormat.length(); i++) {
char c = userFormat.charAt(i);
if (c == '/' || c == '\\') {
if (buffer.length() > 0)
list.add(buffer.toString());
buffer = new StringBuilder();
} else
buffer.append(c);
}
if (buffer.length() > 0)
list.add(buffer.toString());
return list.toArray(new String[0]);
}
public static String convertToUserEditableFormatInternal(String value, boolean locationFormat) {
StringBuilder buffer = new StringBuilder();
if (locationFormat) {
IPath path = Path.fromOSString(value);
if (path.isAbsolute())
return path.toOSString();
int index = value.indexOf(java.io.File.separator);
String variable = index != -1 ? value.substring(0, index) : value;
convertVariableToUserFormat(buffer, variable, variable, false);
if (index != -1)
buffer.append(value.substring(index));
} else {
String components[] = splitVariablesAndContent(value);
for (int i = 0; i < components.length; i++) {
String variable = extractVariable(components[i]);
convertVariableToUserFormat(buffer, components[i], variable, true);
}
}
return buffer.toString();
}
private static void convertVariableToUserFormat(StringBuilder buffer, String component, String variable, boolean generateMacro) {
if (PathVariableUtil.isParentVariable(variable)) {
String argument = PathVariableUtil.getParentVariableArgument(variable);
int count = PathVariableUtil.getParentVariableCount(variable);
if (argument != null && count != -1) {
buffer.append(generateMacro ? PathVariableUtil.buildVariableMacro(Path.fromOSString(argument)) : Path.fromOSString(argument));
for (int j = 0; j < count; j++) {
buffer.append(java.io.File.separator + ".."); //$NON-NLS-1$
}
} else
buffer.append(component);
} else
buffer.append(component);
}
/*
* Splits a value (returned by this.getValue(variable) in an array of
* string, where the array is divided between the value content and the
* value variables.
*
* For example, if the value is "${ECLIPSE_HOME}/plugins", the value
* returned will be {"${ECLIPSE_HOME}" "/plugins"}
*/
static String[] splitVariablesAndContent(String value) {
LinkedList<String> result = new LinkedList<>();
while (true) {
// we check if the value contains referenced variables with ${VAR}
int index = value.indexOf("${"); //$NON-NLS-1$
if (index != -1) {
int endIndex = getMatchingBrace(value, index);
if (index > 0)
result.add(value.substring(0, index));
result.add(value.substring(index, endIndex + 1));
value = value.substring(endIndex + 1);
} else
break;
}
if (value.length() > 0)
result.add(value);
return result.toArray(new String[0]);
}
/*
* Splits a value (returned by this.getValue(variable) in an array of
* string of the variables contained in the value.
*
* For example, if the value is "${ECLIPSE_HOME}/plugins", the value
* returned will be {"ECLIPSE_HOME"}. If the value is
* "${ECLIPSE_HOME}/${FOO}/plugins", the value returned will be
* {"ECLIPSE_HOME", "FOO"}.
*/
static String[] splitVariableNames(String value) {
LinkedList<String> result = new LinkedList<>();
while (true) {
int index = value.indexOf("${"); //$NON-NLS-1$
if (index != -1) {
int endIndex = getMatchingBrace(value, index);
result.add(value.substring(index + 2, endIndex));
value = value.substring(endIndex + 1);
} else
break;
}
return result.toArray(new String[0]);
}
/*
* Extracts the variable name from a variable segment.
*
* For example, if the value is "${ECLIPSE_HOME}", the value returned will
* be "ECLIPSE_HOME". If the segment doesn't contain any variable, the value
* returned will be "".
*/
static String extractVariable(String segment) {
int index = segment.indexOf("${"); //$NON-NLS-1$
if (index != -1) {
int endIndex = getMatchingBrace(segment, index);
return segment.substring(index + 2, endIndex);
}
return ""; //$NON-NLS-1$
}
// getMatchingBrace("${FOO}/something") returns 5
// getMatchingBrace("${${OTHER}}/something") returns 10
// getMatchingBrace("${FOO") returns 5
static int getMatchingBrace(String value, int index) {
int scope = 0;
for (int i = index + 1; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '}') {
if (scope == 0)
return i;
scope--;
}
if (c == '$') {
if ((i + 1 < value.length()) && (value.charAt(i + 1) == '{'))
scope++;
}
}
return value.length();
}
/**
* Returns whether this variable is suited for programmatically determining
* which variable is the most appropriate when creating new linked resources.
*
* @return true if the path variable is preferred.
*/
static public boolean isPreferred(String variableName) {
return !(variableName.equals(WorkspaceLocationVariableResolver.NAME) || variableName.equals(WorkspaceParentLocationVariableResolver.NAME) || variableName.equals(ParentVariableResolver.NAME));
}
}