/******************************************************************************* * Copyright (c) 2005, 2012 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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$ StringBuffer relative = new StringBuffer(); // 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 */ public String toString() { StringBuffer result = new StringBuffer(); if (device != null) result.append(device); if (isAbsolute()) result.append(SEPARATOR); for (int i = 0; i < segments.length; i++) { result.append(segments[i]); result.append(SEPARATOR); } if (segments.length > 0 && !hasTrailingSlash()) result.deleteCharAt(result.length() - 1); return result.toString(); } }