1 /******************************************************************************* 2 * Copyright (c) 2008, 2016 Freescale Semiconductor and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Serge Beauchamp (Freescale Semiconductor) - initial API and implementation 13 * James Blackburn (Broadcom Corp.) - ongoing development 14 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 15 * Mickael Istria (Red Hat Inc.) - Bug 488937 16 *******************************************************************************/ 17 package org.eclipse.core.internal.resources; 18 19 import java.net.URI; 20 import java.util.ArrayList; 21 import java.util.LinkedList; 22 import org.eclipse.core.filesystem.URIUtil; 23 import org.eclipse.core.internal.resources.projectvariables.*; 24 import org.eclipse.core.resources.IPathVariableManager; 25 import org.eclipse.core.resources.IResource; 26 import org.eclipse.core.runtime.*; 27 28 public class PathVariableUtil { 29 getUniqueVariableName(String variable, IResource resource)30 static public String getUniqueVariableName(String variable, IResource resource) { 31 int index = 1; 32 variable = getValidVariableName(variable); 33 StringBuilder destVariable = new StringBuilder(variable); 34 35 IPathVariableManager pathVariableManager = resource.getPathVariableManager(); 36 if (variable.startsWith(ParentVariableResolver.NAME) || variable.startsWith(ProjectLocationVariableResolver.NAME)) 37 destVariable.insert(0, "copy_"); //$NON-NLS-1$ 38 39 while (pathVariableManager.isDefined(destVariable.toString())) { 40 destVariable.append(index); 41 index++; 42 } 43 return destVariable.toString(); 44 } 45 getValidVariableName(String variable)46 public static String getValidVariableName(String variable) { 47 // remove the argument part if the variable is of the form ${VAR-ARG} 48 int argumentIndex = variable.indexOf('-'); 49 if (argumentIndex != -1) 50 variable = variable.substring(0, argumentIndex); 51 52 variable = variable.trim(); 53 char first = variable.charAt(0); 54 if (!Character.isLetter(first) && first != '_') { 55 variable = 'A' + variable; 56 } 57 58 StringBuilder builder = new StringBuilder(); 59 for (int i = 0; i < variable.length(); i++) { 60 char c = variable.charAt(i); 61 if ((Character.isLetter(c) || Character.isDigit(c) || c == '_') && !Character.isWhitespace(c)) 62 builder.append(c); 63 } 64 variable = builder.toString(); 65 return variable; 66 } 67 convertToPathRelativeMacro(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint)68 public static IPath convertToPathRelativeMacro(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { 69 return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, true); 70 } 71 convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint)72 static public IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint) throws CoreException { 73 return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, true, false); 74 } 75 convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint)76 static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint) throws CoreException { 77 return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint, true, false)); 78 } 79 convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro)80 static public URI convertToRelative(IPathVariableManager pathVariableManager, URI originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { 81 return URIUtil.toURI(convertToRelative(pathVariableManager, URIUtil.toPath(originalPath), resource, force, variableHint)); 82 } 83 convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro)84 static private IPath convertToRelative(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean skipWorkspace, boolean generateMacro) throws CoreException { 85 if (variableHint != null && pathVariableManager.isDefined(variableHint)) { 86 IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); 87 if (value != null) 88 return wrapInProperFormat(makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variableHint, generateMacro), generateMacro); 89 } 90 IPath path = convertToProperCase(originalPath); 91 IPath newPath = null; 92 int maxMatchLength = -1; 93 String[] existingVariables = pathVariableManager.getPathVariableNames(); 94 for (String variable : existingVariables) { 95 if (skipWorkspace) { 96 // Variables relative to the workspace are not portable, and defeat the purpose of having linked resource locations, 97 // so they should not automatically be created relative to the workspace. 98 if (variable.equals(WorkspaceLocationVariableResolver.NAME)) 99 continue; 100 } 101 if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) 102 continue; 103 if (variable.equals(ParentVariableResolver.NAME)) 104 continue; 105 // find closest path to the original path 106 IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); 107 if (value != null) { 108 value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); 109 if (value.isPrefixOf(path)) { 110 int matchLength = value.segmentCount(); 111 if (matchLength > maxMatchLength) { 112 maxMatchLength = matchLength; 113 newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); 114 } 115 } 116 } 117 } 118 if (newPath != null) 119 return wrapInProperFormat(newPath, generateMacro); 120 121 if (force) { 122 int originalSegmentCount = originalPath.segmentCount(); 123 for (int j = 0; j <= originalSegmentCount; j++) { 124 IPath matchingPath = path.removeLastSegments(j); 125 int minDifference = Integer.MAX_VALUE; 126 for (String variable : existingVariables) { 127 if (skipWorkspace) { 128 if (variable.equals(WorkspaceLocationVariableResolver.NAME)) 129 continue; 130 } 131 if (variable.equals(WorkspaceParentLocationVariableResolver.NAME)) 132 continue; 133 if (variable.equals(ParentVariableResolver.NAME)) 134 continue; 135 IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); 136 if (value != null) { 137 value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); 138 if (matchingPath.isPrefixOf(value)) { 139 int difference = value.segmentCount() - originalSegmentCount; 140 if (difference < minDifference) { 141 minDifference = difference; 142 newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); 143 } 144 } 145 } 146 } 147 if (newPath != null) 148 return wrapInProperFormat(newPath, generateMacro); 149 } 150 if (originalSegmentCount == 0) { 151 String variable = ProjectLocationVariableResolver.NAME; 152 IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variable)); 153 value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); 154 if (originalPath.isPrefixOf(value)) 155 newPath = makeRelativeToVariable(pathVariableManager, originalPath, resource, force, variable, generateMacro); 156 if (newPath != null) 157 return wrapInProperFormat(newPath, generateMacro); 158 } 159 } 160 161 if (skipWorkspace) 162 return convertToRelative(pathVariableManager, originalPath, resource, force, variableHint, false, generateMacro); 163 return originalPath; 164 } 165 wrapInProperFormat(IPath newPath, boolean generateMacro)166 private static IPath wrapInProperFormat(IPath newPath, boolean generateMacro) { 167 if (generateMacro) 168 newPath = PathVariableUtil.buildVariableMacro(newPath); 169 return newPath; 170 } 171 makeRelativeToVariable(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean generateMacro)172 private static IPath makeRelativeToVariable(IPathVariableManager pathVariableManager, IPath originalPath, IResource resource, boolean force, String variableHint, boolean generateMacro) { 173 IPath path = convertToProperCase(originalPath); 174 IPath value = URIUtil.toPath(pathVariableManager.getURIValue(variableHint)); 175 value = convertToProperCase(URIUtil.toPath(pathVariableManager.resolveURI(URIUtil.toURI(value)))); 176 int valueSegmentCount = value.segmentCount(); 177 if (value.isPrefixOf(path)) { 178 // transform "c:/foo/bar" into "FOO/bar" 179 IPath tmp = Path.fromOSString(variableHint); 180 for (int j = valueSegmentCount; j < originalPath.segmentCount(); j++) { 181 tmp = tmp.append(originalPath.segment(j)); 182 } 183 return tmp; 184 } 185 186 if (force) { 187 if (devicesAreCompatible(path, value)) { 188 // transform "c:/foo/bar/other_child/file.txt" into "${PARENT-1-BAR_CHILD}/other_child/file.txt" 189 int matchingFirstSegments = path.matchingFirstSegments(value); 190 if (matchingFirstSegments >= 0) { 191 String originalName = buildParentPathVariable(variableHint, valueSegmentCount - matchingFirstSegments, true); 192 IPath tmp = Path.fromOSString(originalName); 193 for (int j = matchingFirstSegments; j < originalPath.segmentCount(); j++) { 194 tmp = tmp.append(originalPath.segment(j)); 195 } 196 return tmp; 197 } 198 } 199 } 200 return originalPath; 201 } 202 devicesAreCompatible(IPath path, IPath value)203 private static boolean devicesAreCompatible(IPath path, IPath value) { 204 return (path.getDevice() != null && value.getDevice() != null) ? (path.getDevice().equals(value.getDevice())) : (path.getDevice() == value.getDevice()); 205 } 206 convertToProperCase(IPath path)207 static private IPath convertToProperCase(IPath path) { 208 if (Platform.getOS().equals(Platform.OS_WIN32)) 209 return Path.fromPortableString(path.toPortableString().toLowerCase()); 210 return path; 211 } 212 isParentVariable(String variableString)213 static public boolean isParentVariable(String variableString) { 214 return variableString.startsWith(ParentVariableResolver.NAME + '-'); 215 } 216 217 // the format is PARENT-COUNT-ARGUMENT getParentVariableCount(String variableString)218 static public int getParentVariableCount(String variableString) { 219 String items[] = variableString.split("-"); //$NON-NLS-1$ 220 if (items.length == 3) { 221 try { 222 Integer count = Integer.valueOf(items[1]); 223 return count.intValue(); 224 } catch (NumberFormatException e) { 225 // nothing 226 } 227 } 228 return -1; 229 } 230 231 // the format is PARENT-COUNT-ARGUMENT getParentVariableArgument(String variableString)232 static public String getParentVariableArgument(String variableString) { 233 String items[] = variableString.split("-"); //$NON-NLS-1$ 234 if (items.length == 3) 235 return items[2]; 236 return null; 237 } 238 buildParentPathVariable(String variable, int difference, boolean generateMacro)239 static public String buildParentPathVariable(String variable, int difference, boolean generateMacro) { 240 String newString = ParentVariableResolver.NAME + "-" + difference + "-" + variable; //$NON-NLS-1$//$NON-NLS-2$ 241 242 if (!generateMacro) 243 newString = "${" + newString + "}"; //$NON-NLS-1$//$NON-NLS-2$ 244 return newString; 245 } 246 buildVariableMacro(IPath relativeSrcValue)247 public static IPath buildVariableMacro(IPath relativeSrcValue) { 248 String variable = relativeSrcValue.segment(0); 249 variable = "${" + variable + "}"; //$NON-NLS-1$//$NON-NLS-2$ 250 return Path.fromOSString(variable).append(relativeSrcValue.removeFirstSegments(1)); 251 } 252 convertFromUserEditableFormatInternal(IPathVariableManager manager, String userFormat, boolean locationFormat)253 public static String convertFromUserEditableFormatInternal(IPathVariableManager manager, String userFormat, boolean locationFormat) { 254 char pathPrefix = 0; 255 if ((userFormat.length() > 0) && (userFormat.charAt(0) == '/' || userFormat.charAt(0) == '\\')) 256 pathPrefix = userFormat.charAt(0); 257 String components[] = splitPathComponents(userFormat); 258 for (int i = 0; i < components.length; i++) { 259 if (components[i] == null) 260 continue; 261 if (isDotDot(components[i])) { 262 int parentCount = 1; 263 components[i] = null; 264 for (int j = i + 1; j < components.length; j++) { 265 if (components[j] != null) { 266 if (isDotDot(components[j])) { 267 parentCount++; 268 components[j] = null; 269 } else 270 break; 271 } 272 } 273 if (i == 0) // this means the value is implicitly relative to the project location 274 components[0] = PathVariableUtil.buildParentPathVariable(ProjectLocationVariableResolver.NAME, parentCount, false); 275 else { 276 for (int j = i - 1; j >= 0; j--) { 277 if (parentCount == 0) 278 break; 279 if (components[j] == null) 280 continue; 281 String variable = extractVariable(components[j]); 282 283 boolean hasVariableWithMacroSyntax = true; 284 if (variable.length() == 0 && (locationFormat && j == 0)) { 285 variable = components[j]; 286 hasVariableWithMacroSyntax = false; 287 } 288 289 try { 290 if (variable.length() > 0) { 291 String prefix = ""; //$NON-NLS-1$ 292 if (hasVariableWithMacroSyntax) { 293 int indexOfVariable = components[j].indexOf(variable) - "${".length(); //$NON-NLS-1$ 294 prefix = components[j].substring(0, indexOfVariable); 295 String suffix = components[j].substring(indexOfVariable + "${".length() + variable.length() + "}".length()); //$NON-NLS-1$ //$NON-NLS-2$ 296 if (suffix.length() != 0) { 297 // Create an intermediate variable, since a syntax of "${VAR}foo/../" 298 // can't be converted to a "${PARENT-1-VAR}foo" variable. 299 // So instead, an intermediate variable "VARFOO" will be created of value 300 // "${VAR}foo", and the string "${PARENT-1-VARFOO}" will be inserted. 301 String intermediateVariable = PathVariableUtil.getValidVariableName(variable + suffix); 302 IPath intermediateValue = Path.fromPortableString(components[j]); 303 int intermediateVariableIndex = 1; 304 String originalIntermediateVariableName = intermediateVariable; 305 while (manager.isDefined(intermediateVariable)) { 306 IPath tmpValue = URIUtil.toPath(manager.getURIValue(intermediateVariable)); 307 if (tmpValue.equals(intermediateValue)) 308 break; 309 intermediateVariable = originalIntermediateVariableName + intermediateVariableIndex; 310 } 311 if (!manager.isDefined(intermediateVariable)) 312 manager.setURIValue(intermediateVariable, URIUtil.toURI(intermediateValue)); 313 variable = intermediateVariable; 314 prefix = ""; //$NON-NLS-1$ 315 } 316 } 317 String newVariable = variable; 318 if (PathVariableUtil.isParentVariable(variable)) { 319 String argument = PathVariableUtil.getParentVariableArgument(variable); 320 int count = PathVariableUtil.getParentVariableCount(variable); 321 if (argument != null && count != -1) 322 newVariable = PathVariableUtil.buildParentPathVariable(argument, count + parentCount, locationFormat); 323 else 324 newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); 325 } else 326 newVariable = PathVariableUtil.buildParentPathVariable(variable, parentCount, locationFormat); 327 components[j] = prefix + newVariable; 328 break; 329 } 330 components[j] = null; 331 parentCount--; 332 } catch (CoreException e) { 333 components[j] = null; 334 parentCount--; 335 } 336 } 337 } 338 } 339 } 340 StringBuilder buffer = new StringBuilder(); 341 if (pathPrefix != 0) 342 buffer.append(pathPrefix); 343 for (int i = 0; i < components.length; i++) { 344 if (components[i] != null) { 345 if (i > 0) 346 buffer.append(java.io.File.separator); 347 buffer.append(components[i]); 348 } 349 } 350 return buffer.toString(); 351 } 352 isDotDot(String component)353 private static boolean isDotDot(String component) { 354 return component.equals(".."); //$NON-NLS-1$ 355 } 356 splitPathComponents(String userFormat)357 private static String[] splitPathComponents(String userFormat) { 358 ArrayList<String> list = new ArrayList<>(); 359 StringBuilder buffer = new StringBuilder(); 360 for (int i = 0; i < userFormat.length(); i++) { 361 char c = userFormat.charAt(i); 362 if (c == '/' || c == '\\') { 363 if (buffer.length() > 0) 364 list.add(buffer.toString()); 365 buffer = new StringBuilder(); 366 } else 367 buffer.append(c); 368 } 369 if (buffer.length() > 0) 370 list.add(buffer.toString()); 371 return list.toArray(new String[0]); 372 } 373 convertToUserEditableFormatInternal(String value, boolean locationFormat)374 public static String convertToUserEditableFormatInternal(String value, boolean locationFormat) { 375 StringBuilder buffer = new StringBuilder(); 376 if (locationFormat) { 377 IPath path = Path.fromOSString(value); 378 if (path.isAbsolute()) 379 return path.toOSString(); 380 int index = value.indexOf(java.io.File.separator); 381 String variable = index != -1 ? value.substring(0, index) : value; 382 convertVariableToUserFormat(buffer, variable, variable, false); 383 if (index != -1) 384 buffer.append(value.substring(index)); 385 } else { 386 String components[] = splitVariablesAndContent(value); 387 for (String component : components) { 388 String variable = extractVariable(component); 389 convertVariableToUserFormat(buffer, component, variable, true); 390 } 391 } 392 return buffer.toString(); 393 } 394 convertVariableToUserFormat(StringBuilder buffer, String component, String variable, boolean generateMacro)395 private static void convertVariableToUserFormat(StringBuilder buffer, String component, String variable, boolean generateMacro) { 396 if (PathVariableUtil.isParentVariable(variable)) { 397 String argument = PathVariableUtil.getParentVariableArgument(variable); 398 int count = PathVariableUtil.getParentVariableCount(variable); 399 if (argument != null && count != -1) { 400 buffer.append(generateMacro ? PathVariableUtil.buildVariableMacro(Path.fromOSString(argument)) : Path.fromOSString(argument)); 401 for (int j = 0; j < count; j++) { 402 buffer.append(java.io.File.separator + ".."); //$NON-NLS-1$ 403 } 404 } else 405 buffer.append(component); 406 } else 407 buffer.append(component); 408 } 409 410 /* 411 * Splits a value (returned by this.getValue(variable) in an array of 412 * string, where the array is divided between the value content and the 413 * value variables. 414 * 415 * For example, if the value is "${ECLIPSE_HOME}/plugins", the value 416 * returned will be {"${ECLIPSE_HOME}" "/plugins"} 417 */ splitVariablesAndContent(String value)418 static String[] splitVariablesAndContent(String value) { 419 LinkedList<String> result = new LinkedList<>(); 420 while (true) { 421 // we check if the value contains referenced variables with ${VAR} 422 int index = value.indexOf("${"); //$NON-NLS-1$ 423 if (index != -1) { 424 int endIndex = getMatchingBrace(value, index); 425 if (index > 0) 426 result.add(value.substring(0, index)); 427 result.add(value.substring(index, endIndex + 1)); 428 value = value.substring(endIndex + 1); 429 } else 430 break; 431 } 432 if (value.length() > 0) 433 result.add(value); 434 return result.toArray(new String[0]); 435 } 436 437 /* 438 * Splits a value (returned by this.getValue(variable) in an array of 439 * string of the variables contained in the value. 440 * 441 * For example, if the value is "${ECLIPSE_HOME}/plugins", the value 442 * returned will be {"ECLIPSE_HOME"}. If the value is 443 * "${ECLIPSE_HOME}/${FOO}/plugins", the value returned will be 444 * {"ECLIPSE_HOME", "FOO"}. 445 */ splitVariableNames(String value)446 static String[] splitVariableNames(String value) { 447 LinkedList<String> result = new LinkedList<>(); 448 while (true) { 449 int index = value.indexOf("${"); //$NON-NLS-1$ 450 if (index != -1) { 451 int endIndex = getMatchingBrace(value, index); 452 result.add(value.substring(index + 2, endIndex)); 453 value = value.substring(endIndex + 1); 454 } else 455 break; 456 } 457 return result.toArray(new String[0]); 458 } 459 460 /* 461 * Extracts the variable name from a variable segment. 462 * 463 * For example, if the value is "${ECLIPSE_HOME}", the value returned will 464 * be "ECLIPSE_HOME". If the segment doesn't contain any variable, the value 465 * returned will be "". 466 */ extractVariable(String segment)467 static String extractVariable(String segment) { 468 int index = segment.indexOf("${"); //$NON-NLS-1$ 469 if (index != -1) { 470 int endIndex = getMatchingBrace(segment, index); 471 return segment.substring(index + 2, endIndex); 472 } 473 return ""; //$NON-NLS-1$ 474 } 475 476 // getMatchingBrace("${FOO}/something") returns 5 477 // getMatchingBrace("${${OTHER}}/something") returns 10 478 // getMatchingBrace("${FOO") returns 5 getMatchingBrace(String value, int index)479 static int getMatchingBrace(String value, int index) { 480 int scope = 0; 481 for (int i = index + 1; i < value.length(); i++) { 482 char c = value.charAt(i); 483 if (c == '}') { 484 if (scope == 0) 485 return i; 486 scope--; 487 } 488 if (c == '$') { 489 if ((i + 1 < value.length()) && (value.charAt(i + 1) == '{')) 490 scope++; 491 } 492 } 493 return value.length(); 494 } 495 496 /** 497 * Returns whether this variable is suited for programmatically determining 498 * which variable is the most appropriate when creating new linked resources. 499 * 500 * @return true if the path variable is preferred. 501 */ isPreferred(String variableName)502 static public boolean isPreferred(String variableName) { 503 return !(variableName.equals(WorkspaceLocationVariableResolver.NAME) || variableName.equals(WorkspaceParentLocationVariableResolver.NAME) || variableName.equals(ParentVariableResolver.NAME)); 504 } 505 } 506