Mercurial > projects > dwt-addons
comparison dwtx/core/runtime/Path.d @ 3:6518c18a01f7
eclipse.core package without osgi dependencies
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Wed, 26 Mar 2008 00:57:19 +0100 |
parents | |
children | ea8ff534f622 |
comparison
equal
deleted
inserted
replaced
2:a012107a911c | 3:6518c18a01f7 |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 2006 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.core.runtime.Path; | |
14 | |
15 import tango.io.FilePath; | |
16 import dwtx.core.runtime.IPath; | |
17 import dwtx.core.runtime.Assert; | |
18 | |
19 import dwt.dwthelper.utils; | |
20 | |
21 import tango.io.FileConst; | |
22 | |
23 static import tango.text.Text; | |
24 alias tango.text.Text.Text!(char) StringBuffer; | |
25 | |
26 /** | |
27 * The standard implementation of the <code>IPath</code> interface. | |
28 * Paths are always maintained in canonicalized form. That is, parent | |
29 * references (i.e., <code>../../</code>) and duplicate separators are | |
30 * resolved. For example, | |
31 * <pre> new Path("/a/b").append("../foo/bar")</pre> | |
32 * will yield the path | |
33 * <pre> /a/foo/bar</pre> | |
34 * <p> | |
35 * This class can be used without OSGi running. | |
36 * </p><p> | |
37 * This class is not intended to be subclassed by clients but | |
38 * may be instantiated. | |
39 * </p> | |
40 * @see IPath | |
41 */ | |
42 public class Path : IPath, Cloneable { | |
43 /** masks for separator values */ | |
44 private static const int HAS_LEADING = 1; | |
45 private static const int IS_UNC = 2; | |
46 private static const int HAS_TRAILING = 4; | |
47 | |
48 private static const int ALL_SEPARATORS = HAS_LEADING | IS_UNC | HAS_TRAILING; | |
49 | |
50 /** Constant empty string value. */ | |
51 private static const String EMPTY_STRING = ""; //$NON-NLS-1$ | |
52 | |
53 /** Constant value indicating no segments */ | |
54 private static const String[] NO_SEGMENTS = null; | |
55 | |
56 /** Constant value containing the empty path with no device. */ | |
57 public static const Path EMPTY; | |
58 | |
59 /** Mask for all bits that are involved in the hash code */ | |
60 private static const int HASH_MASK = ~HAS_TRAILING; | |
61 | |
62 | |
63 /** Constant root path string (<code>"/"</code>). */ | |
64 private static const String ROOT_STRING = "/"; //$NON-NLS-1$ | |
65 | |
66 /** Constant value containing the root path with no device. */ | |
67 public static const Path ROOT; | |
68 | |
69 /** Constant value indicating if the current platform is Windows */ | |
70 version(Windows){ | |
71 private static const bool WINDOWS = true; | |
72 } | |
73 else { | |
74 private static const bool WINDOWS = false; | |
75 } | |
76 | |
77 static this(){ | |
78 EMPTY = new Path(EMPTY_STRING); | |
79 ROOT = new Path(ROOT_STRING); | |
80 } | |
81 | |
82 /** The device id string. May be null if there is no device. */ | |
83 private String device = null; | |
84 | |
85 //Private implementation note: the segments and separators | |
86 //arrays are never modified, so that they can be shared between | |
87 //path instances | |
88 | |
89 /** The path segments */ | |
90 private String[] segments_; | |
91 | |
92 /** flags indicating separators (has leading, is UNC, has trailing) */ | |
93 private int separators; | |
94 | |
95 /** | |
96 * Constructs a new path from the given string path. | |
97 * The string path must represent a valid file system path | |
98 * on the local file system. | |
99 * The path is canonicalized and double slashes are removed | |
100 * except at the beginning. (to handle UNC paths). All forward | |
101 * slashes ('/') are treated as segment delimiters, and any | |
102 * segment and device delimiters for the local file system are | |
103 * also respected. | |
104 * | |
105 * @param pathString the portable string path | |
106 * @see IPath#toPortableString() | |
107 * @since 3.1 | |
108 */ | |
109 public static IPath fromOSString(String pathString) { | |
110 return new Path(pathString); | |
111 } | |
112 | |
113 /** | |
114 * Constructs a new path from the given path string. | |
115 * The path string must have been produced by a previous | |
116 * call to <code>IPath.toPortableString</code>. | |
117 * | |
118 * @param pathString the portable path string | |
119 * @see IPath#toPortableString() | |
120 * @since 3.1 | |
121 */ | |
122 public static IPath fromPortableString(String pathString) { | |
123 int firstMatch = pathString.indexOf(DEVICE_SEPARATOR) +1; | |
124 //no extra work required if no device characters | |
125 if (firstMatch <= 0) | |
126 return (new Path()).initialize(null, pathString); | |
127 //if we find a single colon, then the path has a device | |
128 String devicePart = null; | |
129 int pathLength = pathString.length; | |
130 if (firstMatch is pathLength || pathString.charAt(firstMatch) !is DEVICE_SEPARATOR) { | |
131 devicePart = pathString.substring(0, firstMatch); | |
132 pathString = pathString.substring(firstMatch, pathLength); | |
133 } | |
134 //optimize for no colon literals | |
135 if (pathString.indexOf(DEVICE_SEPARATOR) is -1) | |
136 return (new Path()).initialize(devicePart, pathString); | |
137 //contract colon literals | |
138 char[] chars = pathString/+.toCharArray()+/; | |
139 int readOffset = 0, writeOffset = 0, length = chars.length; | |
140 while (readOffset < length) { | |
141 if (chars[readOffset] is DEVICE_SEPARATOR) | |
142 if (++readOffset >= length) | |
143 break; | |
144 chars[writeOffset++] = chars[readOffset++]; | |
145 } | |
146 return (new Path()).initialize(devicePart, chars[ 0 .. writeOffset] ); | |
147 } | |
148 | |
149 /* (Intentionally not included in javadoc) | |
150 * Private constructor. | |
151 */ | |
152 private this() { | |
153 // not allowed | |
154 } | |
155 | |
156 /** | |
157 * Constructs a new path from the given string path. | |
158 * The string path must represent a valid file system path | |
159 * on the local file system. | |
160 * The path is canonicalized and double slashes are removed | |
161 * except at the beginning. (to handle UNC paths). All forward | |
162 * slashes ('/') are treated as segment delimiters, and any | |
163 * segment and device delimiters for the local file system are | |
164 * also respected (such as colon (':') and backslash ('\') on some file systems). | |
165 * | |
166 * @param fullPath the string path | |
167 * @see #isValidPath(String) | |
168 */ | |
169 public this(String fullPath) { | |
170 String devicePart = null; | |
171 if (WINDOWS) { | |
172 //convert backslash to forward slash | |
173 fullPath = fullPath.indexOf('\\') is -1 ? fullPath : fullPath.replace('\\', SEPARATOR); | |
174 //extract device | |
175 int i = fullPath.indexOf(DEVICE_SEPARATOR); | |
176 if (i !is -1) { | |
177 //remove leading slash from device part to handle output of URL.getFile() | |
178 int start = fullPath.charAt(0) is SEPARATOR ? 1 : 0; | |
179 devicePart = fullPath.substring(start, i + 1); | |
180 fullPath = fullPath.substring(i + 1, fullPath.length); | |
181 } | |
182 } | |
183 initialize(devicePart, fullPath); | |
184 } | |
185 | |
186 /** | |
187 * Constructs a new path from the given device id and string path. | |
188 * The given string path must be valid. | |
189 * The path is canonicalized and double slashes are removed except | |
190 * at the beginning (to handle UNC paths). All forward | |
191 * slashes ('/') are treated as segment delimiters, and any | |
192 * segment delimiters for the local file system are | |
193 * also respected (such as backslash ('\') on some file systems). | |
194 * | |
195 * @param device the device id | |
196 * @param path the string path | |
197 * @see #isValidPath(String) | |
198 * @see #setDevice(String) | |
199 */ | |
200 public this(String device, String path) { | |
201 if (WINDOWS) { | |
202 //convert backslash to forward slash | |
203 path = path.indexOf('\\') is -1 ? path : path.replace('\\', SEPARATOR); | |
204 } | |
205 initialize(device, path); | |
206 } | |
207 | |
208 /* (Intentionally not included in javadoc) | |
209 * Private constructor. | |
210 */ | |
211 private this(String device, String[] segments_, int _separators) { | |
212 // no segment validations are done for performance reasons | |
213 this.segments_ = segments_; | |
214 this.device = device; | |
215 //hash code is cached in all but the bottom three bits of the separators field | |
216 this.separators = (computeHashCode() << 3) | (_separators & ALL_SEPARATORS); | |
217 } | |
218 | |
219 /* (Intentionally not included in javadoc) | |
220 * @see IPath#addFileExtension | |
221 */ | |
222 public IPath addFileExtension(String extension) { | |
223 if (isRoot() || isEmpty() || hasTrailingSeparator()) | |
224 return this; | |
225 int len = segments_.length; | |
226 String[] newSegments = new String[len]; | |
227 System.arraycopy(segments_, 0, newSegments, 0, len - 1); | |
228 newSegments[len - 1] = segments_[len - 1] ~ '.' ~ extension; | |
229 return new Path(device, newSegments, separators); | |
230 } | |
231 | |
232 /* (Intentionally not included in javadoc) | |
233 * @see IPath#addTrailingSeparator | |
234 */ | |
235 public IPath addTrailingSeparator() { | |
236 if (hasTrailingSeparator() || isRoot()) { | |
237 return this; | |
238 } | |
239 //XXX workaround, see 1GIGQ9V | |
240 if (isEmpty()) { | |
241 return new Path(device, segments_, HAS_LEADING); | |
242 } | |
243 return new Path(device, segments_, separators | HAS_TRAILING); | |
244 } | |
245 | |
246 /* (Intentionally not included in javadoc) | |
247 * @see IPath#append(IPath) | |
248 */ | |
249 public IPath append(IPath tail) { | |
250 //optimize some easy cases | |
251 if (tail is null || tail.segmentCount() is 0) | |
252 return this; | |
253 //these call chains look expensive, but in most cases they are no-ops | |
254 if (this.isEmpty()) | |
255 return tail.setDevice(device).makeRelative().makeUNC(isUNC()); | |
256 if (this.isRoot()) | |
257 return tail.setDevice(device).makeAbsolute().makeUNC(isUNC()); | |
258 | |
259 //concatenate the two segment arrays | |
260 int myLen = segments_.length; | |
261 int tailLen = tail.segmentCount(); | |
262 String[] newSegments = new String[myLen + tailLen]; | |
263 System.arraycopy(segments_, 0, newSegments, 0, myLen); | |
264 for (int i = 0; i < tailLen; i++) { | |
265 newSegments[myLen + i] = tail.segment(i); | |
266 } | |
267 //use my leading separators and the tail's trailing separator | |
268 Path result = new Path(device, newSegments, (separators & (HAS_LEADING | IS_UNC)) | (tail.hasTrailingSeparator() ? HAS_TRAILING : 0)); | |
269 String tailFirstSegment = newSegments[myLen]; | |
270 if (tailFirstSegment.equals("..") || tailFirstSegment.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$ | |
271 result.canonicalize(); | |
272 } | |
273 return result; | |
274 } | |
275 | |
276 /* (Intentionally not included in javadoc) | |
277 * @see IPath#append(java.lang.String) | |
278 */ | |
279 public IPath append(String tail) { | |
280 //optimize addition of a single segment | |
281 if (tail.indexOf(SEPARATOR) is -1 && tail.indexOf("\\") is -1 && tail.indexOf(DEVICE_SEPARATOR) is -1) { //$NON-NLS-1$ | |
282 int tailLength = tail.length; | |
283 if (tailLength < 3) { | |
284 //some special cases | |
285 if (tailLength is 0 || ".".equals(tail)) { //$NON-NLS-1$ | |
286 return this; | |
287 } | |
288 if ("..".equals(tail)) //$NON-NLS-1$ | |
289 return removeLastSegments(1); | |
290 } | |
291 //just add the segment | |
292 int myLen = segments_.length; | |
293 String[] newSegments = new String[myLen + 1]; | |
294 System.arraycopy(segments_, 0, newSegments, 0, myLen); | |
295 newSegments[myLen] = tail; | |
296 return new Path(device, newSegments, separators & ~HAS_TRAILING); | |
297 } | |
298 //go with easy implementation | |
299 return append(new Path(tail)); | |
300 } | |
301 | |
302 /** | |
303 * Destructively converts this path to its canonical form. | |
304 * <p> | |
305 * In its canonical form, a path does not have any | |
306 * "." segments, and parent references ("..") are collapsed | |
307 * where possible. | |
308 * </p> | |
309 * @return true if the path was modified, and false otherwise. | |
310 */ | |
311 private bool canonicalize() { | |
312 //look for segments that need canonicalizing | |
313 for (int i = 0, max = segments_.length; i < max; i++) { | |
314 String segment = segments_[i]; | |
315 if (segment.charAt(0) is '.' && (segment.equals("..") || segment.equals("."))) { //$NON-NLS-1$ //$NON-NLS-2$ | |
316 //path needs to be canonicalized | |
317 collapseParentReferences(); | |
318 //paths of length 0 have no trailing separator | |
319 if (segments_.length is 0) | |
320 separators &= (HAS_LEADING | IS_UNC); | |
321 //recompute hash because canonicalize affects hash | |
322 separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3); | |
323 return true; | |
324 } | |
325 } | |
326 return false; | |
327 } | |
328 | |
329 /* (Intentionally not included in javadoc) | |
330 * Clones this object. | |
331 */ | |
332 public Path clone() { | |
333 return new Path(device, segments_, separators); | |
334 } | |
335 | |
336 /** | |
337 * Destructively removes all occurrences of ".." segments from this path. | |
338 */ | |
339 private void collapseParentReferences() { | |
340 int segmentCount = segments_.length; | |
341 String[] stack = new String[segmentCount]; | |
342 int stackPointer = 0; | |
343 for (int i = 0; i < segmentCount; i++) { | |
344 String segment = segments_[i]; | |
345 if (segment.equals("..")) { //$NON-NLS-1$ | |
346 if (stackPointer is 0) { | |
347 // if the stack is empty we are going out of our scope | |
348 // so we need to accumulate segments. But only if the original | |
349 // path is relative. If it is absolute then we can't go any higher than | |
350 // root so simply toss the .. references. | |
351 if (!isAbsolute()) | |
352 stack[stackPointer++] = segment; //stack push | |
353 } else { | |
354 // if the top is '..' then we are accumulating segments so don't pop | |
355 if ("..".equals(stack[stackPointer - 1])) //$NON-NLS-1$ | |
356 stack[stackPointer++] = ".."; //$NON-NLS-1$ | |
357 else | |
358 stackPointer--; | |
359 //stack pop | |
360 } | |
361 //collapse current references | |
362 } else if (!segment.equals(".") || segmentCount is 1) //$NON-NLS-1$ | |
363 stack[stackPointer++] = segment; //stack push | |
364 } | |
365 //if the number of segments hasn't changed, then no modification needed | |
366 if (stackPointer is segmentCount) | |
367 return; | |
368 //build the new segment array backwards by popping the stack | |
369 String[] newSegments = new String[stackPointer]; | |
370 System.arraycopy(stack, 0, newSegments, 0, stackPointer); | |
371 this.segments_ = newSegments; | |
372 } | |
373 | |
374 /** | |
375 * Removes duplicate slashes from the given path, with the exception | |
376 * of leading double slash which represents a UNC path. | |
377 */ | |
378 private String collapseSlashes(String path) { | |
379 int length = path.length; | |
380 // if the path is only 0, 1 or 2 chars long then it could not possibly have illegal | |
381 // duplicate slashes. | |
382 if (length < 3) | |
383 return path; | |
384 // check for an occurrence of // in the path. Start at index 1 to ensure we skip leading UNC // | |
385 // If there are no // then there is nothing to collapse so just return. | |
386 if (path.indexOf("//", 1) is -1) //$NON-NLS-1$ | |
387 return path; | |
388 // We found an occurrence of // in the path so do the slow collapse. | |
389 char[] result = new char[path.length]; | |
390 int count = 0; | |
391 bool hasPrevious = false; | |
392 char[] characters = path/+.toCharArray()+/; | |
393 for (int index = 0; index < characters.length; index++) { | |
394 char c = characters[index]; | |
395 if (c is SEPARATOR) { | |
396 if (hasPrevious) { | |
397 // skip double slashes, except for beginning of UNC. | |
398 // note that a UNC path can't have a device. | |
399 if (device is null && index is 1) { | |
400 result[count] = c; | |
401 count++; | |
402 } | |
403 } else { | |
404 hasPrevious = true; | |
405 result[count] = c; | |
406 count++; | |
407 } | |
408 } else { | |
409 hasPrevious = false; | |
410 result[count] = c; | |
411 count++; | |
412 } | |
413 } | |
414 return result[ 0 .. count]; | |
415 } | |
416 | |
417 /* (Intentionally not included in javadoc) | |
418 * Computes the hash code for this object. | |
419 */ | |
420 private int computeHashCode() { | |
421 int hash = device.length is 0 ? 17 : dwt.dwthelper.utils.toHash(device); | |
422 int segmentCount = segments_.length; | |
423 for (int i = 0; i < segmentCount; i++) { | |
424 //this function tends to given a fairly even distribution | |
425 hash = hash * 37 + dwt.dwthelper.utils.toHash(segments_[i]); | |
426 } | |
427 return hash; | |
428 } | |
429 | |
430 /* (Intentionally not included in javadoc) | |
431 * Returns the size of the string that will be created by toString or toOSString. | |
432 */ | |
433 private int computeLength() { | |
434 int length = 0; | |
435 if (device !is null) | |
436 length += device.length; | |
437 if ((separators & HAS_LEADING) !is 0) | |
438 length++; | |
439 if ((separators & IS_UNC) !is 0) | |
440 length++; | |
441 //add the segment lengths | |
442 int max = segments_.length; | |
443 if (max > 0) { | |
444 for (int i = 0; i < max; i++) { | |
445 length += segments_[i].length; | |
446 } | |
447 //add the separator lengths | |
448 length += max - 1; | |
449 } | |
450 if ((separators & HAS_TRAILING) !is 0) | |
451 length++; | |
452 return length; | |
453 } | |
454 | |
455 /* (Intentionally not included in javadoc) | |
456 * Returns the number of segments in the given path | |
457 */ | |
458 private int computeSegmentCount(String path) { | |
459 int len = path.length; | |
460 if (len is 0 || (len is 1 && path.charAt(0) is SEPARATOR)) { | |
461 return 0; | |
462 } | |
463 int count = 1; | |
464 int prev = -1; | |
465 int i; | |
466 while ((i = path.indexOf(SEPARATOR, prev + 1)) !is -1) { | |
467 if (i !is prev + 1 && i !is len) { | |
468 ++count; | |
469 } | |
470 prev = i; | |
471 } | |
472 if (path.charAt(len - 1) is SEPARATOR) { | |
473 --count; | |
474 } | |
475 return count; | |
476 } | |
477 | |
478 /** | |
479 * Computes the segment array for the given canonicalized path. | |
480 */ | |
481 private String[] computeSegments(String path) { | |
482 // performance sensitive --- avoid creating garbage | |
483 int segmentCount = computeSegmentCount(path); | |
484 if (segmentCount is 0) | |
485 return NO_SEGMENTS; | |
486 String[] newSegments = new String[segmentCount]; | |
487 int len = path.length; | |
488 // check for initial slash | |
489 int firstPosition = (path.charAt(0) is SEPARATOR) ? 1 : 0; | |
490 // check for UNC | |
491 if (firstPosition is 1 && len > 1 && (path.charAt(1) is SEPARATOR)) | |
492 firstPosition = 2; | |
493 int lastPosition = (path.charAt(len - 1) !is SEPARATOR) ? len - 1 : len - 2; | |
494 // for non-empty paths, the number of segments is | |
495 // the number of slashes plus 1, ignoring any leading | |
496 // and trailing slashes | |
497 int next = firstPosition; | |
498 for (int i = 0; i < segmentCount; i++) { | |
499 int start = next; | |
500 int end = path.indexOf(SEPARATOR, next); | |
501 if (end is -1) { | |
502 newSegments[i] = path.substring(start, lastPosition + 1); | |
503 } else { | |
504 newSegments[i] = path.substring(start, end); | |
505 } | |
506 next = end + 1; | |
507 } | |
508 return newSegments; | |
509 } | |
510 /** | |
511 * Returns the platform-neutral encoding of the given segment onto | |
512 * the given string buffer. This escapes literal colon characters with double colons. | |
513 */ | |
514 private void encodeSegment(String string, StringBuffer buf) { | |
515 int len = string.length; | |
516 for (int i = 0; i < len; i++) { | |
517 char c = string.charAt(i); | |
518 buf.append(c); | |
519 if (c is DEVICE_SEPARATOR) | |
520 buf.append(DEVICE_SEPARATOR); | |
521 } | |
522 } | |
523 | |
524 /* (Intentionally not included in javadoc) | |
525 * Compares objects for equality. | |
526 */ | |
527 public override int opEquals(Object obj) { | |
528 if (this is obj) | |
529 return true; | |
530 if (!(cast(Path)obj)) | |
531 return false; | |
532 Path target = cast(Path) obj; | |
533 //check leading separators and hash code | |
534 if ((separators & HASH_MASK) !is (target.separators & HASH_MASK)) | |
535 return false; | |
536 String[] targetSegments = target.segments_; | |
537 int i = segments_.length; | |
538 //check segment count | |
539 if (i !is targetSegments.length) | |
540 return false; | |
541 //check segments in reverse order - later segments more likely to differ | |
542 while (--i >= 0) | |
543 if (!segments_[i].equals(targetSegments[i])) | |
544 return false; | |
545 //check device last (least likely to differ) | |
546 return device is target.device || (device !is null && device.equals(target.device)); | |
547 } | |
548 | |
549 /* (Intentionally not included in javadoc) | |
550 * @see IPath#getDevice | |
551 */ | |
552 public String getDevice() { | |
553 return device; | |
554 } | |
555 | |
556 /* (Intentionally not included in javadoc) | |
557 * @see IPath#getFileExtension | |
558 */ | |
559 public String getFileExtension() { | |
560 if (hasTrailingSeparator()) { | |
561 return null; | |
562 } | |
563 String lastSegment = lastSegment(); | |
564 if (lastSegment is null) { | |
565 return null; | |
566 } | |
567 int index = lastSegment.lastIndexOf('.'); | |
568 if (index is -1) { | |
569 return null; | |
570 } | |
571 return lastSegment.substring(index + 1); | |
572 } | |
573 | |
574 /* (Intentionally not included in javadoc) | |
575 * Computes the hash code for this object. | |
576 */ | |
577 public int hashCode() { | |
578 return separators & HASH_MASK; | |
579 } | |
580 | |
581 /* (Intentionally not included in javadoc) | |
582 * @see IPath#hasTrailingSeparator2 | |
583 */ | |
584 public bool hasTrailingSeparator() { | |
585 return (separators & HAS_TRAILING) !is 0; | |
586 } | |
587 | |
588 /* | |
589 * Initialize the current path with the given string. | |
590 */ | |
591 private IPath initialize(String deviceString, String path) { | |
592 Assert.isTrue(path.length > 0); | |
593 this.device = deviceString; | |
594 | |
595 path = collapseSlashes(path); | |
596 int len = path.length; | |
597 | |
598 //compute the separators array | |
599 if (len < 2) { | |
600 if (len is 1 && path.charAt(0) is SEPARATOR) { | |
601 this.separators = HAS_LEADING; | |
602 } else { | |
603 this.separators = 0; | |
604 } | |
605 } else { | |
606 bool hasLeading = path.charAt(0) is SEPARATOR; | |
607 bool isUNC = hasLeading && path.charAt(1) is SEPARATOR; | |
608 //UNC path of length two has no trailing separator | |
609 bool hasTrailing = !(isUNC && len is 2) && path.charAt(len - 1) is SEPARATOR; | |
610 separators = hasLeading ? HAS_LEADING : 0; | |
611 if (isUNC) | |
612 separators |= IS_UNC; | |
613 if (hasTrailing) | |
614 separators |= HAS_TRAILING; | |
615 } | |
616 //compute segments and ensure canonical form | |
617 segments_ = computeSegments(path); | |
618 if (!canonicalize()) { | |
619 //compute hash now because canonicalize didn't need to do it | |
620 separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3); | |
621 } | |
622 return this; | |
623 } | |
624 | |
625 /* (Intentionally not included in javadoc) | |
626 * @see IPath#isAbsolute | |
627 */ | |
628 public bool isAbsolute() { | |
629 //it's absolute if it has a leading separator | |
630 return (separators & HAS_LEADING) !is 0; | |
631 } | |
632 | |
633 /* (Intentionally not included in javadoc) | |
634 * @see IPath#isEmpty | |
635 */ | |
636 public bool isEmpty() { | |
637 //true if no segments and no leading prefix | |
638 return segments_.length is 0 && ((separators & ALL_SEPARATORS) !is HAS_LEADING); | |
639 | |
640 } | |
641 | |
642 /* (Intentionally not included in javadoc) | |
643 * @see IPath#isPrefixOf | |
644 */ | |
645 public bool isPrefixOf(IPath anotherPath) { | |
646 if (device is null) { | |
647 if (anotherPath.getDevice() !is null) { | |
648 return false; | |
649 } | |
650 } else { | |
651 if (!device.equalsIgnoreCase(anotherPath.getDevice())) { | |
652 return false; | |
653 } | |
654 } | |
655 if (isEmpty() || (isRoot() && anotherPath.isAbsolute())) { | |
656 return true; | |
657 } | |
658 int len = segments_.length; | |
659 if (len > anotherPath.segmentCount()) { | |
660 return false; | |
661 } | |
662 for (int i = 0; i < len; i++) { | |
663 if (!segments_[i].equals(anotherPath.segment(i))) | |
664 return false; | |
665 } | |
666 return true; | |
667 } | |
668 | |
669 /* (Intentionally not included in javadoc) | |
670 * @see IPath#isRoot | |
671 */ | |
672 public bool isRoot() { | |
673 //must have no segments, a leading separator, and not be a UNC path. | |
674 return this is ROOT || (segments_.length is 0 && ((separators & ALL_SEPARATORS) is HAS_LEADING)); | |
675 } | |
676 | |
677 /* (Intentionally not included in javadoc) | |
678 * @see IPath#isUNC | |
679 */ | |
680 public bool isUNC() { | |
681 if (device !is null) | |
682 return false; | |
683 return (separators & IS_UNC) !is 0; | |
684 } | |
685 | |
686 /* (Intentionally not included in javadoc) | |
687 * @see IPath#isValidPath(String) | |
688 */ | |
689 public bool isValidPath(String path) { | |
690 Path test = new Path(path); | |
691 for (int i = 0, max = test.segmentCount(); i < max; i++) | |
692 if (!isValidSegment(test.segment(i))) | |
693 return false; | |
694 return true; | |
695 } | |
696 | |
697 /* (Intentionally not included in javadoc) | |
698 * @see IPath#isValidSegment(String) | |
699 */ | |
700 public bool isValidSegment(String segment) { | |
701 int size = segment.length; | |
702 if (size is 0) | |
703 return false; | |
704 for (int i = 0; i < size; i++) { | |
705 char c = segment.charAt(i); | |
706 if (c is '/') | |
707 return false; | |
708 if (WINDOWS && (c is '\\' || c is ':')) | |
709 return false; | |
710 } | |
711 return true; | |
712 } | |
713 | |
714 /* (Intentionally not included in javadoc) | |
715 * @see IPath#lastSegment() | |
716 */ | |
717 public String lastSegment() { | |
718 int len = segments_.length; | |
719 return len is 0 ? null : segments_[len - 1]; | |
720 } | |
721 | |
722 /* (Intentionally not included in javadoc) | |
723 * @see IPath#makeAbsolute() | |
724 */ | |
725 public IPath makeAbsolute() { | |
726 if (isAbsolute()) { | |
727 return this; | |
728 } | |
729 Path result = new Path(device, segments_, separators | HAS_LEADING); | |
730 //may need canonicalizing if it has leading ".." or "." segments | |
731 if (result.segmentCount() > 0) { | |
732 String first = result.segment(0); | |
733 if (first.equals("..") || first.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$ | |
734 result.canonicalize(); | |
735 } | |
736 } | |
737 return result; | |
738 } | |
739 | |
740 /* (Intentionally not included in javadoc) | |
741 * @see IPath#makeRelative() | |
742 */ | |
743 public IPath makeRelative() { | |
744 if (!isAbsolute()) { | |
745 return this; | |
746 } | |
747 return new Path(device, segments_, separators & HAS_TRAILING); | |
748 } | |
749 | |
750 /* (Intentionally not included in javadoc) | |
751 * @see IPath#makeUNC(bool) | |
752 */ | |
753 public IPath makeUNC(bool toUNC) { | |
754 // if we are already in the right form then just return | |
755 if (!(toUNC ^ isUNC())) | |
756 return this; | |
757 | |
758 int newSeparators = this.separators; | |
759 if (toUNC) { | |
760 newSeparators |= HAS_LEADING | IS_UNC; | |
761 } else { | |
762 //mask out the UNC bit | |
763 newSeparators &= HAS_LEADING | HAS_TRAILING; | |
764 } | |
765 return new Path(toUNC ? null : device, segments_, newSeparators); | |
766 } | |
767 | |
768 /* (Intentionally not included in javadoc) | |
769 * @see IPath#matchingFirstSegments(IPath) | |
770 */ | |
771 public int matchingFirstSegments(IPath anotherPath) { | |
772 Assert.isNotNull( cast(Object) anotherPath); | |
773 int anotherPathLen = anotherPath.segmentCount(); | |
774 int max = Math.min(segments_.length, anotherPathLen); | |
775 int count = 0; | |
776 for (int i = 0; i < max; i++) { | |
777 if (!segments_[i].equals(anotherPath.segment(i))) { | |
778 return count; | |
779 } | |
780 count++; | |
781 } | |
782 return count; | |
783 } | |
784 | |
785 /* (Intentionally not included in javadoc) | |
786 * @see IPath#removeFileExtension() | |
787 */ | |
788 public IPath removeFileExtension() { | |
789 String extension = getFileExtension(); | |
790 if (extension is null || extension.equals("")) { //$NON-NLS-1$ | |
791 return this; | |
792 } | |
793 String lastSegment = lastSegment(); | |
794 int index = lastSegment.lastIndexOf(extension) - 1; | |
795 return removeLastSegments(1).append(lastSegment.substring(0, index)); | |
796 } | |
797 | |
798 /* (Intentionally not included in javadoc) | |
799 * @see IPath#removeFirstSegments(int) | |
800 */ | |
801 public IPath removeFirstSegments(int count) { | |
802 if (count is 0) | |
803 return this; | |
804 if (count >= segments_.length) { | |
805 return new Path(device, NO_SEGMENTS, 0); | |
806 } | |
807 Assert.isLegal(count > 0); | |
808 int newSize = segments_.length - count; | |
809 String[] newSegments = new String[newSize]; | |
810 System.arraycopy(this.segments_, count, newSegments, 0, newSize); | |
811 | |
812 //result is always a relative path | |
813 return new Path(device, newSegments, separators & HAS_TRAILING); | |
814 } | |
815 | |
816 /* (Intentionally not included in javadoc) | |
817 * @see IPath#removeLastSegments(int) | |
818 */ | |
819 public IPath removeLastSegments(int count) { | |
820 if (count is 0) | |
821 return this; | |
822 if (count >= segments_.length) { | |
823 //result will have no trailing separator | |
824 return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC)); | |
825 } | |
826 Assert.isLegal(count > 0); | |
827 int newSize = segments_.length - count; | |
828 String[] newSegments = new String[newSize]; | |
829 System.arraycopy(this.segments_, 0, newSegments, 0, newSize); | |
830 return new Path(device, newSegments, separators); | |
831 } | |
832 | |
833 /* (Intentionally not included in javadoc) | |
834 * @see IPath#removeTrailingSeparator() | |
835 */ | |
836 public IPath removeTrailingSeparator() { | |
837 if (!hasTrailingSeparator()) { | |
838 return this; | |
839 } | |
840 return new Path(device, segments_, separators & (HAS_LEADING | IS_UNC)); | |
841 } | |
842 | |
843 /* (Intentionally not included in javadoc) | |
844 * @see IPath#segment(int) | |
845 */ | |
846 public String segment(int index) { | |
847 if (index >= segments_.length) | |
848 return null; | |
849 return segments_[index]; | |
850 } | |
851 | |
852 /* (Intentionally not included in javadoc) | |
853 * @see IPath#segmentCount() | |
854 */ | |
855 public int segmentCount() { | |
856 return segments_.length; | |
857 } | |
858 | |
859 /* (Intentionally not included in javadoc) | |
860 * @see IPath#segments() | |
861 */ | |
862 public String[] segments() { | |
863 String[] segmentCopy = new String[](segments_.length); | |
864 System.arraycopy(segments_, 0, segmentCopy, 0, segments_.length); | |
865 return segmentCopy; | |
866 } | |
867 | |
868 /* (Intentionally not included in javadoc) | |
869 * @see IPath#setDevice(String) | |
870 */ | |
871 public IPath setDevice(String value) { | |
872 if (value !is null) { | |
873 Assert.isTrue(value.indexOf(IPath.DEVICE_SEPARATOR) is (value.length - 1), "Last character should be the device separator"); //$NON-NLS-1$ | |
874 } | |
875 //return the receiver if the device is the same | |
876 if (value is device || (value !is null && value.equals(device))) | |
877 return this; | |
878 | |
879 return new Path(value, segments_, separators); | |
880 } | |
881 | |
882 /* (Intentionally not included in javadoc) | |
883 * @see IPath#toFile() | |
884 */ | |
885 public FilePath toFile() { | |
886 return new FilePath(toOSString()); | |
887 } | |
888 | |
889 /* (Intentionally not included in javadoc) | |
890 * @see IPath#toOSString() | |
891 */ | |
892 public String toOSString() { | |
893 //Note that this method is identical to toString except | |
894 //it uses the OS file separator instead of the path separator | |
895 int resultSize = computeLength(); | |
896 if (resultSize <= 0) | |
897 return EMPTY_STRING; | |
898 char FILE_SEPARATOR = FileConst.PathSeparatorChar; | |
899 char[] result = new char[resultSize]; | |
900 int offset = 0; | |
901 if (device !is null) { | |
902 int size = device.length; | |
903 device.getChars(0, size, result, offset); | |
904 offset += size; | |
905 } | |
906 if ((separators & HAS_LEADING) !is 0) | |
907 result[offset++] = FILE_SEPARATOR; | |
908 if ((separators & IS_UNC) !is 0) | |
909 result[offset++] = FILE_SEPARATOR; | |
910 int len = segments_.length - 1; | |
911 if (len >= 0) { | |
912 //append all but the last segment, with separators | |
913 for (int i = 0; i < len; i++) { | |
914 int size = segments_[i].length; | |
915 segments_[i].getChars(0, size, result, offset); | |
916 offset += size; | |
917 result[offset++] = FILE_SEPARATOR; | |
918 } | |
919 //append the last segment | |
920 int size = segments_[len].length; | |
921 segments_[len].getChars(0, size, result, offset); | |
922 offset += size; | |
923 } | |
924 if ((separators & HAS_TRAILING) !is 0) | |
925 result[offset++] = FILE_SEPARATOR; | |
926 return result; | |
927 } | |
928 | |
929 /* (Intentionally not included in javadoc) | |
930 * @see IPath#toPortableString() | |
931 */ | |
932 public String toPortableString() { | |
933 int resultSize = computeLength(); | |
934 if (resultSize <= 0) | |
935 return EMPTY_STRING; | |
936 StringBuffer result = new StringBuffer(resultSize); | |
937 if (device !is null) | |
938 result.append(device); | |
939 if ((separators & HAS_LEADING) !is 0) | |
940 result.append(SEPARATOR); | |
941 if ((separators & IS_UNC) !is 0) | |
942 result.append(SEPARATOR); | |
943 int len = segments_.length; | |
944 //append all segments with separators | |
945 for (int i = 0; i < len; i++) { | |
946 if (segments_[i].indexOf(DEVICE_SEPARATOR) >= 0) | |
947 encodeSegment(segments_[i], result); | |
948 else | |
949 result.append(segments_[i]); | |
950 if (i < len-1 || (separators & HAS_TRAILING) !is 0) | |
951 result.append(SEPARATOR); | |
952 } | |
953 return result.toString(); | |
954 } | |
955 | |
956 /* (Intentionally not included in javadoc) | |
957 * @see IPath#toString() | |
958 */ | |
959 public String toString() { | |
960 int resultSize = computeLength(); | |
961 if (resultSize <= 0) | |
962 return EMPTY_STRING; | |
963 char[] result = new char[resultSize]; | |
964 int offset = 0; | |
965 if (device !is null) { | |
966 int size = device.length; | |
967 device.getChars(0, size, result, offset); | |
968 offset += size; | |
969 } | |
970 if ((separators & HAS_LEADING) !is 0) | |
971 result[offset++] = SEPARATOR; | |
972 if ((separators & IS_UNC) !is 0) | |
973 result[offset++] = SEPARATOR; | |
974 int len = segments_.length - 1; | |
975 if (len >= 0) { | |
976 //append all but the last segment, with separators | |
977 for (int i = 0; i < len; i++) { | |
978 int size = segments_[i].length; | |
979 segments_[i].getChars(0, size, result, offset); | |
980 offset += size; | |
981 result[offset++] = SEPARATOR; | |
982 } | |
983 //append the last segment | |
984 int size = segments_[len].length; | |
985 segments_[len].getChars(0, size, result, offset); | |
986 offset += size; | |
987 } | |
988 if ((separators & HAS_TRAILING) !is 0) | |
989 result[offset++] = SEPARATOR; | |
990 return result; | |
991 } | |
992 | |
993 /* (Intentionally not included in javadoc) | |
994 * @see IPath#uptoSegment(int) | |
995 */ | |
996 public IPath uptoSegment(int count) { | |
997 if (count is 0) | |
998 return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC)); | |
999 if (count >= segments_.length) | |
1000 return this; | |
1001 Assert.isTrue(count > 0, "Invalid parameter to Path.uptoSegment"); //$NON-NLS-1$ | |
1002 String[] newSegments = new String[count]; | |
1003 System.arraycopy(segments_, 0, newSegments, 0, count); | |
1004 return new Path(device, newSegments, separators); | |
1005 } | |
1006 } |