Copyright (c) 2008, 2016 Freescale Semiconductor 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: Serge Beauchamp (Freescale Semiconductor) - initial API and implementation James Blackburn (Broadcom Corp.) - ongoing development Lars Vogel - Bug 473427 Mickael Istria (Red Hat Inc.) - Bug 488937
/******************************************************************************* * Copyright (c) 2008, 2016 Freescale Semiconductor 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: * 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); StringBuilder destVariable = new StringBuilder(variable); IPathVariableManager pathVariableManager = resource.getPathVariableManager(); if (variable.startsWith(ParentVariableResolver.NAME) || variable.startsWith(ProjectLocationVariableResolver.NAME)) destVariable.insert(0, "copy_"); //$NON-NLS-1$ while (pathVariableManager.isDefined(destVariable.toString())) { destVariable.append(index); index++; } return destVariable.toString(); } 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 (String variable : existingVariables) { 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 (String variable : existingVariables) { 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 (String component : components) { String variable = extractVariable(component); convertVariableToUserFormat(buffer, component, 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.
Returns:true if the path variable is preferred.
/** * 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)); } }