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 }