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