| /******************************************************************************* |
| * Copyright (c) 2005, 2012 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 |
| *******************************************************************************/ |
| package org.eclipse.osgi.framework.util; |
| |
| import java.io.File; |
| |
| /** |
| * A utility class for manipulating file system paths. |
| * <p> |
| * This class is not intended to be subclassed by clients but |
| * may be instantiated. |
| * </p> |
| * |
| * @since 3.1 |
| */ |
| public class FilePath { |
| // Constant value indicating if the current platform is Windows |
| private static final boolean WINDOWS = java.io.File.separatorChar == '\\'; |
| private final static String CURRENT_DIR = "."; //$NON-NLS-1$ |
| // Device separator character constant ":" used in paths. |
| private static final char DEVICE_SEPARATOR = ':'; |
| private static final byte HAS_LEADING = 1; |
| private static final byte HAS_TRAILING = 4; |
| // Constant value indicating no segments |
| private static final String[] NO_SEGMENTS = new String[0]; |
| private final static String PARENT_DIR = ".."; //$NON-NLS-1$ |
| private final static char SEPARATOR = '/'; |
| private final static String UNC_SLASHES = "//"; //$NON-NLS-1$ |
| // if UNC, device will be \\host\share, otherwise, it will be letter/name + colon |
| private String device; |
| private byte flags; |
| private String[] segments; |
| |
| /** |
| * Constructs a new file path from the given File object. |
| * |
| * @param location |
| */ |
| public FilePath(File location) { |
| initialize(location.getPath()); |
| if (location.isDirectory()) |
| flags |= HAS_TRAILING; |
| else |
| flags &= ~HAS_TRAILING; |
| } |
| |
| /** |
| * Constructs a new file path from the given string path. |
| * |
| * @param original |
| */ |
| public FilePath(String original) { |
| initialize(original); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * Splits the given path string into an array of segments. |
| */ |
| private String[] computeSegments(String path) { |
| int maxSegmentCount = computeSegmentCount(path); |
| if (maxSegmentCount == 0) |
| return NO_SEGMENTS; |
| String[] newSegments = new String[maxSegmentCount]; |
| int len = path.length(); |
| // allways absolute |
| int firstPosition = isAbsolute() ? 1 : 0; |
| int lastPosition = hasTrailingSlash() ? len - 2 : len - 1; |
| // for non-empty paths, the number of segments is |
| // the number of slashes plus 1, ignoring any leading |
| // and trailing slashes |
| int next = firstPosition; |
| int actualSegmentCount = 0; |
| for (int i = 0; i < maxSegmentCount; i++) { |
| int start = next; |
| int end = path.indexOf(SEPARATOR, next); |
| next = end + 1; |
| String segment = path.substring(start, end == -1 ? lastPosition + 1 : end); |
| if (CURRENT_DIR.equals(segment)) |
| continue; |
| if (PARENT_DIR.equals(segment)) { |
| if (actualSegmentCount > 0) |
| actualSegmentCount--; |
| continue; |
| } |
| newSegments[actualSegmentCount++] = segment; |
| } |
| if (actualSegmentCount == newSegments.length) |
| return newSegments; |
| if (actualSegmentCount == 0) |
| return NO_SEGMENTS; |
| String[] actualSegments = new String[actualSegmentCount]; |
| System.arraycopy(newSegments, 0, actualSegments, 0, actualSegments.length); |
| return actualSegments; |
| } |
| |
| /** |
| * Returns the device for this file system path, or <code>null</code> if |
| * none exists. The device string ends with a colon. |
| * |
| * @return the device string or null |
| */ |
| public String getDevice() { |
| return device; |
| } |
| |
| /** |
| * Returns the segments in this path. If this path has no segments, returns an empty array. |
| * |
| * @return an array containing all segments for this path |
| */ |
| public String[] getSegments() { |
| return segments.clone(); |
| } |
| |
| /** |
| * Returns whether this path ends with a slash. |
| * |
| * @return <code>true</code> if the path ends with a slash, false otherwise |
| */ |
| public boolean hasTrailingSlash() { |
| return (flags & HAS_TRAILING) != 0; |
| } |
| |
| private void initialize(String original) { |
| original = original.indexOf('\\') == -1 ? original : original.replace('\\', SEPARATOR); |
| if (WINDOWS) { |
| // only deal with devices/UNC paths on Windows |
| int deviceSeparatorPos = original.indexOf(DEVICE_SEPARATOR); |
| if (deviceSeparatorPos >= 0) { |
| //extract device if any |
| //remove leading slash from device part to handle output of URL.getFile() |
| int start = original.charAt(0) == SEPARATOR ? 1 : 0; |
| device = original.substring(start, deviceSeparatorPos + 1); |
| original = original.substring(deviceSeparatorPos + 1, original.length()); |
| } else if (original.startsWith(UNC_SLASHES)) { |
| // handle UNC paths |
| int uncPrefixEnd = original.indexOf(SEPARATOR, 2); |
| if (uncPrefixEnd >= 0) |
| uncPrefixEnd = original.indexOf(SEPARATOR, uncPrefixEnd + 1); |
| if (uncPrefixEnd >= 0) { |
| device = original.substring(0, uncPrefixEnd); |
| original = original.substring(uncPrefixEnd, original.length()); |
| } else |
| // not a valid UNC |
| throw new IllegalArgumentException("Not a valid UNC: " + original); //$NON-NLS-1$ |
| } |
| } |
| // device names letters and UNCs properly stripped off |
| if (original.charAt(0) == SEPARATOR) |
| flags |= HAS_LEADING; |
| if (original.charAt(original.length() - 1) == SEPARATOR) |
| flags |= HAS_TRAILING; |
| segments = computeSegments(original); |
| } |
| |
| /** |
| * Returns whether this path is absolute (begins with a slash). |
| * |
| * @return <code>true</code> if this path is absolute, <code>false</code> otherwise |
| */ |
| public boolean isAbsolute() { |
| return (flags & HAS_LEADING) != 0; |
| } |
| |
| /** |
| * Returns a string representing this path as a relative to the given base path. |
| * <p> |
| * If this path and the given path do not use the same device letter, this path's |
| * string representation is returned as is. |
| * </p> |
| * |
| * @param base the path this path should be made relative to |
| * @return a string representation for this path as relative to the given base path |
| */ |
| public String makeRelative(FilePath base) { |
| if (base.device != null && !base.device.equalsIgnoreCase(this.device)) |
| return base.toString(); |
| int baseCount = this.segments.length; |
| int count = this.matchingFirstSegments(base); |
| if (baseCount == count && count == base.segments.length) |
| return base.hasTrailingSlash() ? ("." + SEPARATOR) : "."; //$NON-NLS-1$ //$NON-NLS-2$ |
| StringBuilder relative = new StringBuilder(); // |
| for (int j = 0; j < baseCount - count; j++) |
| relative.append(PARENT_DIR + SEPARATOR); |
| for (int i = 0; i < base.segments.length - count; i++) { |
| relative.append(base.segments[count + i]); |
| relative.append(SEPARATOR); |
| } |
| if (!base.hasTrailingSlash()) |
| relative.deleteCharAt(relative.length() - 1); |
| return relative.toString(); |
| } |
| |
| /* |
| * Returns the number of segments in this matching the first segments of the |
| * given path. |
| */ |
| private int matchingFirstSegments(FilePath anotherPath) { |
| int anotherPathLen = anotherPath.segments.length; |
| int max = Math.min(segments.length, anotherPathLen); |
| int count = 0; |
| for (int i = 0; i < max; i++) { |
| if (!segments[i].equals(anotherPath.segments[i])) |
| return count; |
| count++; |
| } |
| return count; |
| } |
| |
| /** |
| * Returns a string representation of this path. |
| * |
| * @return a string representation of this path |
| */ |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| if (device != null) |
| result.append(device); |
| if (isAbsolute()) |
| result.append(SEPARATOR); |
| for (String segment : segments) { |
| result.append(segment); |
| result.append(SEPARATOR); |
| } |
| if (segments.length > 0 && !hasTrailingSlash()) |
| result.deleteCharAt(result.length() - 1); |
| return result.toString(); |
| } |
| } |