| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| * Patrick Tasse - Add extra constructor to Path class (bug 454959) |
| *******************************************************************************/ |
| package org.eclipse.core.runtime; |
| |
| import java.io.File; |
| import java.util.Arrays; |
| |
| /** |
| * The standard implementation of the <code>IPath</code> interface. |
| * Paths are always maintained in canonicalized form. That is, parent |
| * references (i.e., <code>../../</code>) and duplicate separators are |
| * resolved. For example, |
| * <pre> new Path("/a/b").append("../foo/bar")</pre> |
| * will yield the path |
| * <pre> /a/foo/bar</pre> |
| * <p> |
| * This class can be used without OSGi running. |
| * </p><p> |
| * This class is not intended to be subclassed by clients but |
| * may be instantiated. |
| * </p> |
| * @see IPath |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class Path implements IPath, Cloneable { |
| /** masks for flag values */ |
| private static final int HAS_LEADING = 1; |
| private static final int IS_UNC = 2; |
| private static final int HAS_TRAILING = 4; |
| private static final int IS_FOR_WINDOWS = 8; |
| |
| private static final int ALL_SEPARATORS = HAS_LEADING | IS_UNC | HAS_TRAILING; |
| private static final int ALL_FLAGS = HAS_LEADING | IS_UNC | HAS_TRAILING | IS_FOR_WINDOWS; |
| |
| /** Constant value indicating if the current platform is Windows */ |
| private static final boolean RUNNING_ON_WINDOWS = java.io.File.separatorChar == '\\'; |
| |
| /** Constant empty string value. */ |
| private static final String EMPTY_STRING = ""; //$NON-NLS-1$ |
| |
| /** Constant value indicating no segments */ |
| private static final String[] NO_SEGMENTS = new String[0]; |
| |
| /** Constant value containing the empty path with no device on the local file system. */ |
| public static final Path EMPTY = new Path(EMPTY_STRING); |
| |
| /** Mask for all bits that are involved in the hash code */ |
| private static final int HASH_MASK = ~(HAS_TRAILING | IS_FOR_WINDOWS); |
| |
| /** Constant root path string (<code>"/"</code>). */ |
| private static final String ROOT_STRING = "/"; //$NON-NLS-1$ |
| |
| /** Constant value containing the root path with no device on the local file system. */ |
| public static final Path ROOT = new Path(ROOT_STRING); |
| |
| /** The device id string. May be null if there is no device. */ |
| private String device = null; |
| |
| //Private implementation note: the segments array and flag bitmap |
| //are never modified, so that they can be shared between path instances |
| |
| /** The path segments */ |
| private String[] segments; |
| |
| /** flags indicating separators (has leading, is UNC, has trailing, is for Windows) */ |
| private int flags; |
| |
| /** |
| * Constructs a new path from the given string path. |
| * The string path must represent a valid file system path |
| * on the local file system. |
| * The path is canonicalized and double slashes are removed |
| * except at the beginning. (to handle UNC paths). All forward |
| * slashes ('/') are treated as segment delimiters, and any |
| * segment and device delimiters for the local file system are |
| * also respected. |
| * |
| * @param pathString the portable string path |
| * @see IPath#toPortableString() |
| * @since 3.1 |
| */ |
| public static IPath fromOSString(String pathString) { |
| return new Path(pathString); |
| } |
| |
| /** |
| * Constructs a new path from the given path string. |
| * The path string must have been produced by a previous |
| * call to <code>IPath.toPortableString</code>. |
| * |
| * @param pathString the portable path string |
| * @see IPath#toPortableString() |
| * @since 3.1 |
| */ |
| public static IPath fromPortableString(String pathString) { |
| int firstMatch = pathString.indexOf(DEVICE_SEPARATOR) + 1; |
| //no extra work required if no device characters |
| if (firstMatch <= 0) |
| return new Path().initialize(null, pathString, RUNNING_ON_WINDOWS); |
| //if we find a single colon, then the path has a device |
| String devicePart = null; |
| int pathLength = pathString.length(); |
| if (firstMatch == pathLength || pathString.charAt(firstMatch) != DEVICE_SEPARATOR) { |
| devicePart = pathString.substring(0, firstMatch); |
| pathString = pathString.substring(firstMatch, pathLength); |
| } |
| //optimize for no colon literals |
| if (pathString.indexOf(DEVICE_SEPARATOR) == -1) |
| return new Path().initialize(devicePart, pathString, RUNNING_ON_WINDOWS); |
| //contract colon literals |
| char[] chars = pathString.toCharArray(); |
| int readOffset = 0, writeOffset = 0, length = chars.length; |
| while (readOffset < length) { |
| if (chars[readOffset] == DEVICE_SEPARATOR) |
| if (++readOffset >= length) |
| break; |
| chars[writeOffset++] = chars[readOffset++]; |
| } |
| return new Path().initialize(devicePart, new String(chars, 0, writeOffset), RUNNING_ON_WINDOWS); |
| } |
| |
| /** |
| * Constructs a new POSIX path from the given string path. The string path |
| * must represent a valid file system path on a POSIX file system. The path |
| * is canonicalized and double slashes are removed except at the beginning |
| * (to handle UNC paths). All forward slashes ('/') are treated as segment |
| * delimiters. This factory method should be used if the string path is for |
| * a POSIX file system. |
| * |
| * @param fullPath the string path |
| * @see #isValidPosixPath(String) |
| * @since 3.7 |
| */ |
| public static Path forPosix(String fullPath) { |
| return new Path(fullPath, false); |
| } |
| |
| /** |
| * Constructs a new Windows path from the given string path. The string path |
| * must represent a valid file system path on the Windows file system. The |
| * path is canonicalized and double slashes are removed except at the |
| * beginning (to handle UNC paths). All forward slashes ('/') are treated as |
| * segment delimiters, and any segment ('\') and device (':') delimiters for |
| * the Windows file system are also respected. This factory method should be |
| * used if the string path is for the Windows file system. |
| * |
| * @param fullPath the string path |
| * @see #isValidWindowsPath(String) |
| * @since 3.7 |
| */ |
| public static Path forWindows(String fullPath) { |
| return new Path(fullPath, true); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Private constructor. |
| */ |
| private Path() { |
| // not allowed |
| } |
| |
| /** |
| * Constructs a new path from the given string path. |
| * The string path must represent a valid file system path |
| * on the local file system. |
| * The path is canonicalized and double slashes are removed |
| * except at the beginning. (to handle UNC paths). All forward |
| * slashes ('/') are treated as segment delimiters, and any |
| * segment and device delimiters for the local file system are |
| * also respected (such as colon (':') and backslash ('\') on some file systems). |
| * This constructor should be used if the string path if for the local file system. |
| * |
| * @param fullPath the string path |
| * @see #isValidPath(String) |
| */ |
| public Path(String fullPath) { |
| this(fullPath, RUNNING_ON_WINDOWS); |
| } |
| |
| /** |
| * Constructs a new path from the given device id and string path. |
| * The given string path must be valid. |
| * The path is canonicalized and double slashes are removed except |
| * at the beginning (to handle UNC paths). All forward |
| * slashes ('/') are treated as segment delimiters, and any |
| * segment delimiters for the local file system are |
| * also respected (such as backslash ('\') on some file systems). |
| * |
| * @param device the device id |
| * @param path the string path |
| * @see #isValidPath(String) |
| * @see #setDevice(String) |
| */ |
| public Path(String device, String path) { |
| if (RUNNING_ON_WINDOWS) { |
| //convert backslash to forward slash |
| path = path.indexOf('\\') == -1 ? path : path.replace('\\', SEPARATOR); |
| } |
| initialize(device, path, RUNNING_ON_WINDOWS); |
| } |
| |
| /** |
| * Constructs a new path from the given string path. The string path must |
| * represent a valid file system path on the specified file system. The path |
| * is canonicalized and double slashes are removed except at the beginning |
| * (to handle UNC paths). All forward slashes ('/') are treated as segment |
| * delimiters, and any segment and device delimiters for the specified file |
| * system are also respected (such as colon (':') and backslash ('\') on |
| * Windows). |
| * |
| * @param fullPath the string path |
| * @param forWindows true if the string path is for the Windows file system |
| * @since 3.7 |
| */ |
| private Path(String fullPath, boolean forWindows) { |
| String devicePart = null; |
| if (forWindows) { |
| //convert backslash to forward slash |
| fullPath = fullPath.indexOf('\\') == -1 ? fullPath : fullPath.replace('\\', SEPARATOR); |
| //extract device |
| int i = fullPath.indexOf(DEVICE_SEPARATOR); |
| if (i != -1) { |
| //remove leading slash from device part to handle output of URL.getFile() |
| int start = fullPath.charAt(0) == SEPARATOR ? 1 : 0; |
| devicePart = fullPath.substring(start, i + 1); |
| fullPath = fullPath.substring(i + 1, fullPath.length()); |
| } |
| } |
| initialize(devicePart, fullPath, forWindows); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Private constructor. |
| */ |
| private Path(String device, String[] segments, int flags) { |
| // no segment validations are done for performance reasons |
| this.segments = segments; |
| this.device = device; |
| //hash code is cached in all but the bottom four bits of the flags field |
| this.flags = (computeHashCode() << 4) | (flags & ALL_FLAGS); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#addFileExtension |
| */ |
| @Override |
| public IPath addFileExtension(String extension) { |
| if (isRoot() || isEmpty() || hasTrailingSeparator()) |
| return this; |
| int len = segments.length; |
| String[] newSegments = new String[len]; |
| System.arraycopy(segments, 0, newSegments, 0, len - 1); |
| newSegments[len - 1] = segments[len - 1] + '.' + extension; |
| return new Path(device, newSegments, flags); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#addTrailingSeparator |
| */ |
| @Override |
| public IPath addTrailingSeparator() { |
| if (hasTrailingSeparator() || isRoot()) { |
| return this; |
| } |
| //XXX workaround, see 1GIGQ9V |
| if (isEmpty()) { |
| return new Path(device, segments, (flags & IS_FOR_WINDOWS) | HAS_LEADING); |
| } |
| return new Path(device, segments, flags | HAS_TRAILING); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#append(IPath) |
| */ |
| @Override |
| public IPath append(IPath tail) { |
| //optimize some easy cases |
| if (tail == null || tail.segmentCount() == 0) |
| return this; |
| //these call chains look expensive, but in most cases they are no-ops |
| //the tail must be for the same platform as this instance |
| if (this.isEmpty() && ((flags & IS_FOR_WINDOWS) == 0) == tail.isValidSegment(":")) //$NON-NLS-1$ |
| return tail.setDevice(device).makeRelative().makeUNC(isUNC()); |
| if (this.isRoot() && ((flags & IS_FOR_WINDOWS) == 0) == tail.isValidSegment(":")) //$NON-NLS-1$ |
| return tail.setDevice(device).makeAbsolute().makeUNC(isUNC()); |
| |
| //concatenate the two segment arrays |
| int myLen = segments.length; |
| int tailLen = tail.segmentCount(); |
| String[] newSegments = new String[myLen + tailLen]; |
| System.arraycopy(segments, 0, newSegments, 0, myLen); |
| for (int i = 0; i < tailLen; i++) { |
| newSegments[myLen + i] = tail.segment(i); |
| } |
| //use my leading separators and the tail's trailing separator |
| Path result = new Path(device, newSegments, (flags & (HAS_LEADING | IS_UNC | IS_FOR_WINDOWS)) | (tail.hasTrailingSeparator() ? HAS_TRAILING : 0)); |
| String tailFirstSegment = newSegments[myLen]; |
| if (tailFirstSegment.equals("..") || tailFirstSegment.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| result.canonicalize(); |
| } |
| return result; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#append(java.lang.String) |
| */ |
| @Override |
| public IPath append(String tail) { |
| //optimize addition of a single segment |
| if (tail.indexOf(SEPARATOR) == -1 && tail.indexOf('\\') == -1 && tail.indexOf(DEVICE_SEPARATOR) == -1) { //$NON-NLS-1$ |
| int tailLength = tail.length(); |
| if (tailLength < 3) { |
| //some special cases |
| if (tailLength == 0 || ".".equals(tail)) { //$NON-NLS-1$ |
| return this; |
| } |
| if ("..".equals(tail)) //$NON-NLS-1$ |
| return removeLastSegments(1); |
| } |
| //just add the segment |
| int myLen = segments.length; |
| String[] newSegments = new String[myLen + 1]; |
| System.arraycopy(segments, 0, newSegments, 0, myLen); |
| newSegments[myLen] = tail; |
| return new Path(device, newSegments, flags & ~HAS_TRAILING); |
| } |
| //go with easy implementation |
| return append(new Path(tail, (flags & IS_FOR_WINDOWS) != 0)); |
| } |
| |
| /** |
| * Destructively converts this path to its canonical form. |
| * <p> |
| * In its canonical form, a path does not have any |
| * "." segments, and parent references ("..") are collapsed |
| * where possible. |
| * </p> |
| * @return true if the path was modified, and false otherwise. |
| */ |
| private boolean canonicalize() { |
| //look for segments that need canonicalizing |
| for (int i = 0, max = segments.length; i < max; i++) { |
| String segment = segments[i]; |
| if (segment.charAt(0) == '.' && (segment.equals("..") || segment.equals("."))) { //$NON-NLS-1$ //$NON-NLS-2$ |
| //path needs to be canonicalized |
| collapseParentReferences(); |
| //paths of length 0 have no trailing separator |
| if (segments.length == 0) |
| flags &= (HAS_LEADING | IS_UNC); |
| //recompute hash because canonicalize affects hash |
| flags = (flags & ALL_FLAGS) | (computeHashCode() << 4); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Clones this object. |
| */ |
| @Override |
| public Object clone() { |
| try { |
| return super.clone(); |
| } catch (CloneNotSupportedException e) { |
| return null; |
| } |
| } |
| |
| /** |
| * Destructively removes all occurrences of ".." segments from this path. |
| */ |
| private void collapseParentReferences() { |
| int segmentCount = segments.length; |
| String[] stack = new String[segmentCount]; |
| int stackPointer = 0; |
| for (int i = 0; i < segmentCount; i++) { |
| String segment = segments[i]; |
| if (segment.equals("..")) { //$NON-NLS-1$ |
| if (stackPointer == 0) { |
| // if the stack is empty we are going out of our scope |
| // so we need to accumulate segments. But only if the original |
| // path is relative. If it is absolute then we can't go any higher than |
| // root so simply toss the .. references. |
| if (!isAbsolute()) |
| stack[stackPointer++] = segment; //stack push |
| } else { |
| // if the top is '..' then we are accumulating segments so don't pop |
| if ("..".equals(stack[stackPointer - 1])) //$NON-NLS-1$ |
| stack[stackPointer++] = ".."; //$NON-NLS-1$ |
| else |
| stackPointer--; |
| //stack pop |
| } |
| //collapse current references |
| } else if (!segment.equals(".") || segmentCount == 1) //$NON-NLS-1$ |
| stack[stackPointer++] = segment; //stack push |
| } |
| //if the number of segments hasn't changed, then no modification needed |
| if (stackPointer == segmentCount) |
| return; |
| //build the new segment array backwards by popping the stack |
| String[] newSegments = new String[stackPointer]; |
| System.arraycopy(stack, 0, newSegments, 0, stackPointer); |
| this.segments = newSegments; |
| } |
| |
| /** |
| * Removes duplicate slashes from the given path, with the exception |
| * of leading double slash which represents a UNC path. |
| */ |
| private String collapseSlashes(String path) { |
| int length = path.length(); |
| // if the path is only 0, 1 or 2 chars long then it could not possibly have illegal |
| // duplicate slashes. |
| if (length < 3) |
| return path; |
| // check for an occurrence of // in the path. Start at index 1 to ensure we skip leading UNC // |
| // If there are no // then there is nothing to collapse so just return. |
| if (path.indexOf("//", 1) == -1) //$NON-NLS-1$ |
| return path; |
| // We found an occurrence of // in the path so do the slow collapse. |
| char[] result = new char[path.length()]; |
| int count = 0; |
| boolean hasPrevious = false; |
| char[] characters = path.toCharArray(); |
| for (int index = 0; index < characters.length; index++) { |
| char c = characters[index]; |
| if (c == SEPARATOR) { |
| if (hasPrevious) { |
| // skip double slashes, except for beginning of UNC. |
| // note that a UNC path can't have a device. |
| if (device == null && index == 1) { |
| result[count] = c; |
| count++; |
| } |
| } else { |
| hasPrevious = true; |
| result[count] = c; |
| count++; |
| } |
| } else { |
| hasPrevious = false; |
| result[count] = c; |
| count++; |
| } |
| } |
| return new String(result, 0, count); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Computes the hash code for this object. |
| */ |
| private int computeHashCode() { |
| int hash = device == null ? 17 : device.hashCode(); |
| int segmentCount = segments.length; |
| for (int i = 0; i < segmentCount; i++) { |
| //this function tends to given a fairly even distribution |
| hash = hash * 37 + segments[i].hashCode(); |
| } |
| return hash; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Returns the size of the string that will be created by toString or toOSString. |
| */ |
| private int computeLength() { |
| int length = 0; |
| if (device != null) |
| length += device.length(); |
| if ((flags & HAS_LEADING) != 0) |
| length++; |
| if ((flags & IS_UNC) != 0) |
| length++; |
| //add the segment lengths |
| int max = segments.length; |
| if (max > 0) { |
| for (int i = 0; i < max; i++) { |
| length += segments[i].length(); |
| } |
| //add the separator lengths |
| length += max - 1; |
| } |
| if ((flags & HAS_TRAILING) != 0) |
| length++; |
| return length; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Returns the number of segments in the given path |
| */ |
| private int computeSegmentCount(String path) { |
| int len = path.length(); |
| if (len == 0 || (len == 1 && path.charAt(0) == SEPARATOR)) { |
| return 0; |
| } |
| int count = 1; |
| int prev = -1; |
| int i; |
| while ((i = path.indexOf(SEPARATOR, prev + 1)) != -1) { |
| if (i != prev + 1 && i != len) { |
| ++count; |
| } |
| prev = i; |
| } |
| if (path.charAt(len - 1) == SEPARATOR) { |
| --count; |
| } |
| return count; |
| } |
| |
| /** |
| * Computes the segment array for the given canonicalized path. |
| */ |
| private String[] computeSegments(String path) { |
| // performance sensitive --- avoid creating garbage |
| int segmentCount = computeSegmentCount(path); |
| if (segmentCount == 0) |
| return NO_SEGMENTS; |
| String[] newSegments = new String[segmentCount]; |
| int len = path.length(); |
| // check for initial slash |
| int firstPosition = (path.charAt(0) == SEPARATOR) ? 1 : 0; |
| // check for UNC |
| if (firstPosition == 1 && len > 1 && (path.charAt(1) == SEPARATOR)) |
| firstPosition = 2; |
| int lastPosition = (path.charAt(len - 1) != SEPARATOR) ? len - 1 : len - 2; |
| // for non-empty paths, the number of segments is |
| // the number of slashes plus 1, ignoring any leading |
| // and trailing slashes |
| int next = firstPosition; |
| for (int i = 0; i < segmentCount; i++) { |
| int start = next; |
| int end = path.indexOf(SEPARATOR, next); |
| if (end == -1) { |
| newSegments[i] = path.substring(start, lastPosition + 1); |
| } else { |
| newSegments[i] = path.substring(start, end); |
| } |
| next = end + 1; |
| } |
| return newSegments; |
| } |
| |
| /** |
| * Returns the platform-neutral encoding of the given segment onto |
| * the given string buffer. This escapes literal colon characters with double colons. |
| */ |
| private void encodeSegment(String string, StringBuffer buf) { |
| int len = string.length(); |
| for (int i = 0; i < len; i++) { |
| char c = string.charAt(i); |
| buf.append(c); |
| if (c == DEVICE_SEPARATOR) |
| buf.append(DEVICE_SEPARATOR); |
| } |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Compares objects for equality. |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (!(obj instanceof Path)) |
| return false; |
| Path target = (Path) obj; |
| //check leading separators and hash code |
| if ((flags & HASH_MASK) != (target.flags & HASH_MASK)) |
| return false; |
| String[] targetSegments = target.segments; |
| int i = segments.length; |
| //check segment count |
| if (i != targetSegments.length) |
| return false; |
| //check segments in reverse order - later segments more likely to differ |
| while (--i >= 0) |
| if (!segments[i].equals(targetSegments[i])) |
| return false; |
| //check device last (least likely to differ) |
| return device == target.device || (device != null && device.equals(target.device)); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#getDevice |
| */ |
| @Override |
| public String getDevice() { |
| return device; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#getFileExtension |
| */ |
| @Override |
| public String getFileExtension() { |
| if (hasTrailingSeparator()) { |
| return null; |
| } |
| String lastSegment = lastSegment(); |
| if (lastSegment == null) { |
| return null; |
| } |
| int index = lastSegment.lastIndexOf('.'); |
| if (index == -1) { |
| return null; |
| } |
| return lastSegment.substring(index + 1); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * Computes the hash code for this object. |
| */ |
| @Override |
| public int hashCode() { |
| return flags & HASH_MASK; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#hasTrailingSeparator2 |
| */ |
| @Override |
| public boolean hasTrailingSeparator() { |
| return (flags & HAS_TRAILING) != 0; |
| } |
| |
| /* |
| * Initialize the current path with the given string. |
| */ |
| private IPath initialize(String deviceString, String path, boolean forWindows) { |
| Assert.isNotNull(path); |
| this.device = deviceString; |
| |
| path = collapseSlashes(path); |
| int len = path.length(); |
| |
| //compute the flags bitmap |
| if (len < 2) { |
| if (len == 1 && path.charAt(0) == SEPARATOR) { |
| this.flags = HAS_LEADING; |
| } else { |
| this.flags = 0; |
| } |
| } else { |
| boolean hasLeading = path.charAt(0) == SEPARATOR; |
| boolean isUNC = hasLeading && path.charAt(1) == SEPARATOR; |
| //UNC path of length two has no trailing separator |
| boolean hasTrailing = !(isUNC && len == 2) && path.charAt(len - 1) == SEPARATOR; |
| flags = hasLeading ? HAS_LEADING : 0; |
| if (isUNC) |
| flags |= IS_UNC; |
| if (hasTrailing) |
| flags |= HAS_TRAILING; |
| } |
| if (forWindows) { |
| flags |= IS_FOR_WINDOWS; |
| } |
| //compute segments and ensure canonical form |
| segments = computeSegments(path); |
| if (!canonicalize()) { |
| //compute hash now because canonicalize didn't need to do it |
| flags = (flags & ALL_FLAGS) | (computeHashCode() << 4); |
| } |
| return this; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#isAbsolute |
| */ |
| @Override |
| public boolean isAbsolute() { |
| //it's absolute if it has a leading separator |
| return (flags & HAS_LEADING) != 0; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#isEmpty |
| */ |
| @Override |
| public boolean isEmpty() { |
| //true if no segments and no leading prefix |
| return segments.length == 0 && ((flags & ALL_SEPARATORS) != HAS_LEADING); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#isPrefixOf |
| */ |
| @Override |
| public boolean isPrefixOf(IPath anotherPath) { |
| if (device == null) { |
| if (anotherPath.getDevice() != null) { |
| return false; |
| } |
| } else { |
| if (!device.equalsIgnoreCase(anotherPath.getDevice())) { |
| return false; |
| } |
| } |
| if (isEmpty() || (isRoot() && anotherPath.isAbsolute())) { |
| return true; |
| } |
| int len = segments.length; |
| if (len > anotherPath.segmentCount()) { |
| return false; |
| } |
| for (int i = 0; i < len; i++) { |
| if (!segments[i].equals(anotherPath.segment(i))) |
| return false; |
| } |
| return true; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#isRoot |
| */ |
| @Override |
| public boolean isRoot() { |
| //must have no segments, a leading separator, and not be a UNC path. |
| return this == ROOT || (segments.length == 0 && ((flags & ALL_SEPARATORS) == HAS_LEADING)); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#isUNC |
| */ |
| @Override |
| public boolean isUNC() { |
| if (device != null) |
| return false; |
| return (flags & IS_UNC) != 0; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#isValidPath(String) |
| */ |
| @Override |
| public boolean isValidPath(String path) { |
| return isValidPath(path, (flags & IS_FOR_WINDOWS) != 0); |
| } |
| |
| /** |
| * Returns whether the given string is syntactically correct as a path on a |
| * POSIX file system. The path is correct if each of the segments in its |
| * canonicalized form is valid. |
| * |
| * @param path the path to check |
| * @return <code>true</code> if the given string is a valid path, |
| * and <code>false</code> otherwise |
| * @see #isValidPosixSegment(String) |
| * @since 3.7 |
| */ |
| public static boolean isValidPosixPath(String path) { |
| return isValidPath(path, false); |
| } |
| |
| /** |
| * Returns whether the given string is syntactically correct as a path on |
| * the Windows file system. The device id is the prefix up to and including |
| * the device separator (':'); the path proper is everything to the right of |
| * it, or the entire string if there is no device separator. The device id |
| * is not checked for validity; the path proper is correct if each of the |
| * segments in its canonicalized form is valid. |
| * |
| * @param path the path to check |
| * @return <code>true</code> if the given string is a valid path, |
| * and <code>false</code> otherwise |
| * @see #isValidWindowsSegment(String) |
| * @since 3.7 |
| */ |
| public static boolean isValidWindowsPath(String path) { |
| return isValidPath(path, true); |
| } |
| |
| /** |
| * Returns whether the given string is syntactically correct as a path on |
| * the specified file system. The device id is the prefix up to and |
| * including the device separator for the specified file system; the path |
| * proper is everything to the right of it, or the entire string if there is |
| * no device separator. When the specified platform is a file system with no |
| * meaningful device separator, the entire string is treated as the path |
| * proper. The device id is not checked for validity; the path proper is |
| * correct if each of the segments in its canonicalized form is valid. |
| * |
| * @param path the path to check |
| * @param forWindows true if the path is for the Windows file system |
| * @return <code>true</code> if the given string is a valid path, |
| * and <code>false</code> otherwise |
| * @see #isValidSegment(String, boolean) |
| * @since 3.7 |
| */ |
| private static boolean isValidPath(String path, boolean forWindows) { |
| Path test = new Path(path, forWindows); |
| for (int i = 0, max = test.segmentCount(); i < max; i++) |
| if (!Path.isValidSegment(test.segment(i), forWindows)) |
| return false; |
| return true; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#isValidSegment(String) |
| */ |
| @Override |
| public boolean isValidSegment(String segment) { |
| return isValidSegment(segment, (flags & IS_FOR_WINDOWS) != 0); |
| } |
| |
| /** |
| * Returns whether the given string is valid as a segment in a path on a |
| * POSIX file system. The rules for valid segments are as follows: |
| * <ul> |
| * <li>the empty string is not valid |
| * <li>any string containing the slash character ('/') is not valid |
| * </ul> |
| * |
| * @param segment the path segment to check |
| * @return <code>true</code> if the given path segment is valid, |
| * and <code>false</code> otherwise |
| * @since 3.7 |
| */ |
| public static boolean isValidPosixSegment(String segment) { |
| return isValidSegment(segment, false); |
| } |
| |
| /** |
| * Returns whether the given string is valid as a segment in a path on the |
| * Windows file system. The rules for valid segments are as follows: |
| * <ul> |
| * <li>the empty string is not valid |
| * <li>any string containing the slash character ('/') is not valid |
| * <li>any string containing segment ('\') or device (':') separator |
| * characters is not valid |
| * </ul> |
| * |
| * @param segment the path segment to check |
| * @return <code>true</code> if the given path segment is valid, |
| * and <code>false</code> otherwise |
| * @since 3.7 |
| */ |
| public static boolean isValidWindowsSegment(String segment) { |
| return isValidSegment(segment, true); |
| } |
| |
| /** |
| * Returns whether the given string is valid as a segment in a path on the |
| * specified file system. The rules for valid segments are as follows: |
| * <ul> |
| * <li>the empty string is not valid |
| * <li>any string containing the slash character ('/') is not valid |
| * <li>any string containing segment or device separator characters on the |
| * specified file system, such as the backslash ('\') and colon (':') on |
| * Windows, is not valid |
| * </ul> |
| * |
| * @param segment the path segment to check |
| * @param forWindows true if the path is for the Windows file system |
| * @return <code>true</code> if the given path segment is valid, |
| * and <code>false</code> otherwise |
| * @since 3.7 |
| */ |
| private static boolean isValidSegment(String segment, boolean forWindows) { |
| int size = segment.length(); |
| if (size == 0) |
| return false; |
| for (int i = 0; i < size; i++) { |
| char c = segment.charAt(i); |
| if (c == '/') |
| return false; |
| if (forWindows && (c == '\\' || c == ':')) |
| return false; |
| } |
| return true; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#lastSegment() |
| */ |
| @Override |
| public String lastSegment() { |
| int len = segments.length; |
| return len == 0 ? null : segments[len - 1]; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#makeAbsolute() |
| */ |
| @Override |
| public IPath makeAbsolute() { |
| if (isAbsolute()) { |
| return this; |
| } |
| Path result = new Path(device, segments, flags | HAS_LEADING); |
| //may need canonicalizing if it has leading ".." or "." segments |
| if (result.segmentCount() > 0) { |
| String first = result.segment(0); |
| if (first.equals("..") || first.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| result.canonicalize(); |
| } |
| } |
| return result; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#makeRelative() |
| */ |
| @Override |
| public IPath makeRelative() { |
| if (!isAbsolute()) { |
| return this; |
| } |
| return new Path(device, segments, flags & (HAS_TRAILING | IS_FOR_WINDOWS)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @since org.eclipse.equinox.common 3.5 |
| */ |
| @Override |
| public IPath makeRelativeTo(IPath base) { |
| //can't make relative if devices are not equal |
| if (device != base.getDevice() && (device == null || !device.equalsIgnoreCase(base.getDevice()))) |
| return this; |
| int commonLength = matchingFirstSegments(base); |
| final int differenceLength = base.segmentCount() - commonLength; |
| final int newSegmentLength = differenceLength + segmentCount() - commonLength; |
| if (newSegmentLength == 0) |
| return Path.EMPTY; |
| String[] newSegments = new String[newSegmentLength]; |
| //add parent references for each segment different from the base |
| Arrays.fill(newSegments, 0, differenceLength, ".."); //$NON-NLS-1$ |
| //append the segments of this path not in common with the base |
| System.arraycopy(segments, commonLength, newSegments, differenceLength, newSegmentLength - differenceLength); |
| return new Path(null, newSegments, flags & (HAS_TRAILING | IS_FOR_WINDOWS)); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#makeUNC(boolean) |
| */ |
| @Override |
| public IPath makeUNC(boolean toUNC) { |
| // if we are already in the right form then just return |
| if (!(toUNC ^ isUNC())) |
| return this; |
| |
| int newSeparators = this.flags; |
| if (toUNC) { |
| newSeparators |= HAS_LEADING | IS_UNC; |
| } else { |
| //mask out the UNC bit |
| newSeparators &= HAS_LEADING | HAS_TRAILING | IS_FOR_WINDOWS; |
| } |
| return new Path(toUNC ? null : device, segments, newSeparators); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#matchingFirstSegments(IPath) |
| */ |
| @Override |
| public int matchingFirstSegments(IPath anotherPath) { |
| Assert.isNotNull(anotherPath); |
| int anotherPathLen = anotherPath.segmentCount(); |
| int max = Math.min(segments.length, anotherPathLen); |
| int count = 0; |
| for (int i = 0; i < max; i++) { |
| if (!segments[i].equals(anotherPath.segment(i))) { |
| return count; |
| } |
| count++; |
| } |
| return count; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#removeFileExtension() |
| */ |
| @Override |
| public IPath removeFileExtension() { |
| String extension = getFileExtension(); |
| if (extension == null || extension.equals("")) { //$NON-NLS-1$ |
| return this; |
| } |
| String lastSegment = lastSegment(); |
| int index = lastSegment.lastIndexOf(extension) - 1; |
| return removeLastSegments(1).append(lastSegment.substring(0, index)); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#removeFirstSegments(int) |
| */ |
| @Override |
| public IPath removeFirstSegments(int count) { |
| if (count == 0) |
| return this; |
| if (count >= segments.length) { |
| return new Path(device, NO_SEGMENTS, flags & IS_FOR_WINDOWS); |
| } |
| Assert.isLegal(count > 0); |
| int newSize = segments.length - count; |
| String[] newSegments = new String[newSize]; |
| System.arraycopy(this.segments, count, newSegments, 0, newSize); |
| |
| //result is always a relative path |
| return new Path(device, newSegments, flags & (HAS_TRAILING | IS_FOR_WINDOWS)); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#removeLastSegments(int) |
| */ |
| @Override |
| public IPath removeLastSegments(int count) { |
| if (count == 0) |
| return this; |
| if (count >= segments.length) { |
| //result will have no trailing separator |
| return new Path(device, NO_SEGMENTS, flags & (HAS_LEADING | IS_UNC | IS_FOR_WINDOWS)); |
| } |
| Assert.isLegal(count > 0); |
| int newSize = segments.length - count; |
| String[] newSegments = new String[newSize]; |
| System.arraycopy(this.segments, 0, newSegments, 0, newSize); |
| return new Path(device, newSegments, flags); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#removeTrailingSeparator() |
| */ |
| @Override |
| public IPath removeTrailingSeparator() { |
| if (!hasTrailingSeparator()) { |
| return this; |
| } |
| return new Path(device, segments, flags & (HAS_LEADING | IS_UNC | IS_FOR_WINDOWS)); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#segment(int) |
| */ |
| @Override |
| public String segment(int index) { |
| if (index >= segments.length) |
| return null; |
| return segments[index]; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#segmentCount() |
| */ |
| @Override |
| public int segmentCount() { |
| return segments.length; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#segments() |
| */ |
| @Override |
| public String[] segments() { |
| String[] segmentCopy = new String[segments.length]; |
| System.arraycopy(segments, 0, segmentCopy, 0, segments.length); |
| return segmentCopy; |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#setDevice(String) |
| */ |
| @Override |
| public IPath setDevice(String value) { |
| if (value != null) { |
| Assert.isTrue(value.indexOf(IPath.DEVICE_SEPARATOR) == (value.length() - 1), "Last character should be the device separator"); //$NON-NLS-1$ |
| } |
| //return the receiver if the device is the same |
| if (value == device || (value != null && value.equals(device))) |
| return this; |
| |
| return new Path(value, segments, flags); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#toFile() |
| */ |
| @Override |
| public File toFile() { |
| return new File(toOSString()); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#toOSString() |
| */ |
| @Override |
| public String toOSString() { |
| //Note that this method is identical to toString except |
| //it uses the OS file separator instead of the path separator |
| int resultSize = computeLength(); |
| if (resultSize <= 0) |
| return EMPTY_STRING; |
| char FILE_SEPARATOR = File.separatorChar; |
| char[] result = new char[resultSize]; |
| int offset = 0; |
| if (device != null) { |
| int size = device.length(); |
| device.getChars(0, size, result, offset); |
| offset += size; |
| } |
| if ((flags & HAS_LEADING) != 0) |
| result[offset++] = FILE_SEPARATOR; |
| if ((flags & IS_UNC) != 0) |
| result[offset++] = FILE_SEPARATOR; |
| int len = segments.length - 1; |
| if (len >= 0) { |
| //append all but the last segment, with file separators |
| for (int i = 0; i < len; i++) { |
| int size = segments[i].length(); |
| segments[i].getChars(0, size, result, offset); |
| offset += size; |
| result[offset++] = FILE_SEPARATOR; |
| } |
| //append the last segment |
| int size = segments[len].length(); |
| segments[len].getChars(0, size, result, offset); |
| offset += size; |
| } |
| if ((flags & HAS_TRAILING) != 0) |
| result[offset++] = FILE_SEPARATOR; |
| return new String(result); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#toPortableString() |
| */ |
| @Override |
| public String toPortableString() { |
| int resultSize = computeLength(); |
| if (resultSize <= 0) |
| return EMPTY_STRING; |
| StringBuffer result = new StringBuffer(resultSize); |
| if (device != null) |
| result.append(device); |
| if ((flags & HAS_LEADING) != 0) |
| result.append(SEPARATOR); |
| if ((flags & IS_UNC) != 0) |
| result.append(SEPARATOR); |
| int len = segments.length; |
| //append all segments with separators |
| for (int i = 0; i < len; i++) { |
| if (segments[i].indexOf(DEVICE_SEPARATOR) >= 0) |
| encodeSegment(segments[i], result); |
| else |
| result.append(segments[i]); |
| if (i < len - 1 || (flags & HAS_TRAILING) != 0) |
| result.append(SEPARATOR); |
| } |
| return result.toString(); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#toString() |
| */ |
| @Override |
| public String toString() { |
| int resultSize = computeLength(); |
| if (resultSize <= 0) |
| return EMPTY_STRING; |
| char[] result = new char[resultSize]; |
| int offset = 0; |
| if (device != null) { |
| int size = device.length(); |
| device.getChars(0, size, result, offset); |
| offset += size; |
| } |
| if ((flags & HAS_LEADING) != 0) |
| result[offset++] = SEPARATOR; |
| if ((flags & IS_UNC) != 0) |
| result[offset++] = SEPARATOR; |
| int len = segments.length - 1; |
| if (len >= 0) { |
| //append all but the last segment, with separators |
| for (int i = 0; i < len; i++) { |
| int size = segments[i].length(); |
| segments[i].getChars(0, size, result, offset); |
| offset += size; |
| result[offset++] = SEPARATOR; |
| } |
| //append the last segment |
| int size = segments[len].length(); |
| segments[len].getChars(0, size, result, offset); |
| offset += size; |
| } |
| if ((flags & HAS_TRAILING) != 0) |
| result[offset++] = SEPARATOR; |
| return new String(result); |
| } |
| |
| /* (Intentionally not included in javadoc) |
| * @see IPath#uptoSegment(int) |
| */ |
| @Override |
| public IPath uptoSegment(int count) { |
| if (count == 0) |
| return new Path(device, NO_SEGMENTS, flags & (HAS_LEADING | IS_UNC | IS_FOR_WINDOWS)); |
| if (count >= segments.length) |
| return this; |
| Assert.isTrue(count > 0, "Invalid parameter to Path.uptoSegment"); //$NON-NLS-1$ |
| String[] newSegments = new String[count]; |
| System.arraycopy(segments, 0, newSegments, 0, count); |
| return new Path(device, newSegments, flags); |
| } |
| } |