blob: e1d5cfc591a6c2f8874c149d9447036e626b4356 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}