Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: bcd99d310e1b91f028972cb63bd16b88e88ec2d6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/*******************************************************************************
 * 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$
		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();
	}
}

Back to the top