/******************************************************************************* * Copyright (c) 2008, 2011 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 *******************************************************************************/ package org.eclipse.core.internal.resources; import org.eclipse.core.internal.resources.projectvariables.ParentVariableResolver; import org.eclipse.core.internal.resources.projectvariables.WorkspaceLocationVariableResolver; import org.eclipse.core.runtime.IPath; import org.eclipse.core.internal.resources.projectvariables.WorkspaceParentLocationVariableResolver; 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; while (resource.getPathVariableManager().isDefined(destVariable)) { destVariable = variable + 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; } StringBuffer builder = new StringBuffer(); 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 = new String(); 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 = new String(); } } 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--; } } } } } StringBuffer buffer = new StringBuffer(); 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<String>(); StringBuffer buffer = new StringBuffer(); 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 StringBuffer(); } 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) { StringBuffer buffer = new StringBuffer(); 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(StringBuffer 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<String>(); 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<String>(); 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)); } }