diff org.eclipse.equinox.common/src/org/eclipse/core/runtime/Path.d @ 12:bc29606a740c

Added dwt-addons in original directory structure of eclipse.org
author Frank Benoit <benoit@tionex.de>
date Sat, 14 Mar 2009 18:23:29 +0100
parents
children dccb717aa902
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Path.d	Sat Mar 14 18:23:29 2009 +0100
@@ -0,0 +1,1009 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module org.eclipse.core.runtime.Path;
+
+import tango.io.FilePath;
+static import tango.io.Path;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Assert;
+
+import java.lang.all;
+
+import tango.io.model.IFile;
+
+
+static import tango.text.Text;
+alias tango.text.Text.Text!(char) StringBuffer;
+
+/**
+ * The standard implementation of the <code>IPath</code> interface.
+ * Paths are always maintained in canonicalized form.  That is, parent
+ * references (i.e., <code>../../</code>) and duplicate separators are
+ * resolved.  For example,
+ * <pre>     new Path("/a/b").append("../foo/bar")</pre>
+ * will yield the path
+ * <pre>     /a/foo/bar</pre>
+ * <p>
+ * This class can be used without OSGi running.
+ * </p><p>
+ * This class is not intended to be subclassed by clients but
+ * may be instantiated.
+ * </p>
+ * @see IPath
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class Path : IPath, Cloneable {
+    /** masks for separator values */
+    private static const int HAS_LEADING = 1;
+    private static const int IS_UNC = 2;
+    private static const int HAS_TRAILING = 4;
+
+    private static const int ALL_SEPARATORS = HAS_LEADING | IS_UNC | HAS_TRAILING;
+
+    /** Constant empty string value. */
+    private static const String EMPTY_STRING = ""; //$NON-NLS-1$
+
+    /** Constant value indicating no segments */
+    private static const String[] NO_SEGMENTS = null;
+
+    /** Constant value containing the empty path with no device. */
+    public static const Path EMPTY;
+
+    /** Mask for all bits that are involved in the hash code */
+    private static const int HASH_MASK = ~HAS_TRAILING;
+
+    /** Constant root path string (<code>"/"</code>). */
+    private static const String ROOT_STRING = "/"; //$NON-NLS-1$
+
+    /** Constant value containing the root path with no device. */
+    public static const Path ROOT;
+
+    /** Constant value indicating if the current platform is Windows */
+    version(Windows){
+        private static const bool WINDOWS = true;
+    }
+    else {
+        private static const bool WINDOWS = false;
+    }
+
+    static this(){
+        EMPTY = new Path(EMPTY_STRING);
+        ROOT = new Path(ROOT_STRING);
+    }
+
+    /** The device id string. May be null if there is no device. */
+    private String device = null;
+
+    //Private implementation note: the segments and separators
+    //arrays are never modified, so that they can be shared between
+    //path instances
+
+    /** The path segments */
+    private String[] segments_;
+
+    /** flags indicating separators (has leading, is UNC, has trailing) */
+    private int separators;
+
+    /**
+     * Constructs a new path from the given string path.
+     * The string path must represent a valid file system path
+     * on the local file system.
+     * The path is canonicalized and double slashes are removed
+     * except at the beginning. (to handle UNC paths). All forward
+     * slashes ('/') are treated as segment delimiters, and any
+     * segment and device delimiters for the local file system are
+     * also respected.
+     *
+     * @param pathString the portable string path
+     * @see IPath#toPortableString()
+     * @since 3.1
+     */
+    public static IPath fromOSString(String pathString) {
+        return new Path(pathString);
+    }
+
+    /**
+     * Constructs a new path from the given path string.
+     * The path string must have been produced by a previous
+     * call to <code>IPath.toPortableString</code>.
+     *
+     * @param pathString the portable path string
+     * @see IPath#toPortableString()
+     * @since 3.1
+     */
+    public static IPath fromPortableString(String pathString) {
+        int firstMatch = pathString.indexOf(DEVICE_SEPARATOR) + 1;
+        //no extra work required if no device characters
+        if (firstMatch <= 0)
+            return (new Path()).initialize(null, pathString);
+        //if we find a single colon, then the path has a device
+        String devicePart = null;
+        int pathLength = pathString.length;
+        if (firstMatch is pathLength || pathString.charAt(firstMatch) !is DEVICE_SEPARATOR) {
+            devicePart = pathString.substring(0, firstMatch);
+            pathString = pathString.substring(firstMatch, pathLength);
+        }
+        //optimize for no colon literals
+        if (pathString.indexOf(DEVICE_SEPARATOR) is -1)
+            return (new Path()).initialize(devicePart, pathString);
+        //contract colon literals
+        char[] chars = pathString/+.toCharArray()+/;
+        int readOffset = 0, writeOffset = 0, length = chars.length;
+        while (readOffset < length) {
+            if (chars[readOffset] is DEVICE_SEPARATOR)
+                if (++readOffset >= length)
+                    break;
+            chars[writeOffset++] = chars[readOffset++];
+        }
+        return (new Path()).initialize(devicePart, chars[ 0 .. writeOffset] );
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Private constructor.
+     */
+    private this() {
+        // not allowed
+    }
+
+    /**
+     * Constructs a new path from the given string path.
+     * The string path must represent a valid file system path
+     * on the local file system.
+     * The path is canonicalized and double slashes are removed
+     * except at the beginning. (to handle UNC paths). All forward
+     * slashes ('/') are treated as segment delimiters, and any
+     * segment and device delimiters for the local file system are
+     * also respected (such as colon (':') and backslash ('\') on some file systems).
+     *
+     * @param fullPath the string path
+     * @see #isValidPath(String)
+     */
+    public this(String fullPath) {
+        String devicePart = null;
+        if (WINDOWS) {
+            //convert backslash to forward slash
+            fullPath = fullPath.indexOf('\\') is -1 ? fullPath : fullPath.replace('\\', SEPARATOR);
+            //extract device
+            int i = fullPath.indexOf(DEVICE_SEPARATOR);
+            if (i !is -1) {
+                //remove leading slash from device part to handle output of URL.getFile()
+                int start = fullPath.charAt(0) is SEPARATOR ? 1 : 0;
+                devicePart = fullPath.substring(start, i + 1);
+                fullPath = fullPath.substring(i + 1, fullPath.length);
+            }
+        }
+        initialize(devicePart, fullPath);
+    }
+
+    /**
+     * Constructs a new path from the given device id and string path.
+     * The given string path must be valid.
+     * The path is canonicalized and double slashes are removed except
+     * at the beginning (to handle UNC paths). All forward
+     * slashes ('/') are treated as segment delimiters, and any
+     * segment delimiters for the local file system are
+     * also respected (such as backslash ('\') on some file systems).
+     *
+     * @param device the device id
+     * @param path the string path
+     * @see #isValidPath(String)
+     * @see #setDevice(String)
+     */
+    public this(String device, String path) {
+        if (WINDOWS) {
+            //convert backslash to forward slash
+            path = path.indexOf('\\') is -1 ? path : path.replace('\\', SEPARATOR);
+        }
+        initialize(device, path);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Private constructor.
+     */
+    private this(String device, String[] segments_, int _separators) {
+        // no segment validations are done for performance reasons
+        this.segments_ = segments_;
+        this.device = device;
+        //hash code is cached in all but the bottom three bits of the separators field
+        this.separators = (computeHashCode() << 3) | (_separators & ALL_SEPARATORS);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#addFileExtension
+     */
+    public IPath addFileExtension(String extension) {
+        if (isRoot() || isEmpty() || hasTrailingSeparator())
+            return this;
+        int len = segments_.length;
+        String[] newSegments = new String[len];
+        System.arraycopy(segments_, 0, newSegments, 0, len - 1);
+        newSegments[len - 1] = segments_[len - 1] ~ '.' ~ extension;
+        return new Path(device, newSegments, separators);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#addTrailingSeparator
+     */
+    public IPath addTrailingSeparator() {
+        if (hasTrailingSeparator() || isRoot()) {
+            return this;
+        }
+        //XXX workaround, see 1GIGQ9V
+        if (isEmpty()) {
+            return new Path(device, segments_, HAS_LEADING);
+        }
+        return new Path(device, segments_, separators | HAS_TRAILING);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#append(IPath)
+     */
+    public IPath append(IPath tail) {
+        //optimize some easy cases
+        if (tail is null || tail.segmentCount() is 0)
+            return this;
+        //these call chains look expensive, but in most cases they are no-ops
+        if (this.isEmpty())
+            return tail.setDevice(device).makeRelative().makeUNC(isUNC());
+        if (this.isRoot())
+            return tail.setDevice(device).makeAbsolute().makeUNC(isUNC());
+
+        //concatenate the two segment arrays
+        int myLen = segments_.length;
+        int tailLen = tail.segmentCount();
+        String[] newSegments = new String[myLen + tailLen];
+        System.arraycopy(segments_, 0, newSegments, 0, myLen);
+        for (int i = 0; i < tailLen; i++) {
+            newSegments[myLen + i] = tail.segment(i);
+        }
+        //use my leading separators and the tail's trailing separator
+        Path result = new Path(device, newSegments, (separators & (HAS_LEADING | IS_UNC)) | (tail.hasTrailingSeparator() ? HAS_TRAILING : 0));
+        String tailFirstSegment = newSegments[myLen];
+        if (tailFirstSegment.equals("..") || tailFirstSegment.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$
+            result.canonicalize();
+        }
+        return result;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#append(java.lang.String)
+     */
+    public IPath append(String tail) {
+        //optimize addition of a single segment
+        if (tail.indexOf(SEPARATOR) is -1 && tail.indexOf("\\") is -1 && tail.indexOf(DEVICE_SEPARATOR) is -1) { //$NON-NLS-1$
+            int tailLength = tail.length;
+            if (tailLength < 3) {
+                //some special cases
+                if (tailLength is 0 || ".".equals(tail)) { //$NON-NLS-1$
+                    return this;
+                }
+                if ("..".equals(tail)) //$NON-NLS-1$
+                    return removeLastSegments(1);
+            }
+            //just add the segment
+            int myLen = segments_.length;
+            String[] newSegments = new String[myLen + 1];
+            System.arraycopy(segments_, 0, newSegments, 0, myLen);
+            newSegments[myLen] = tail;
+            return new Path(device, newSegments, separators & ~HAS_TRAILING);
+        }
+        //go with easy implementation
+        return append(new Path(tail));
+    }
+
+    /**
+     * Destructively converts this path to its canonical form.
+     * <p>
+     * In its canonical form, a path does not have any
+     * "." segments, and parent references ("..") are collapsed
+     * where possible.
+     * </p>
+     * @return true if the path was modified, and false otherwise.
+     */
+    private bool canonicalize() {
+        //look for segments that need canonicalizing
+        for (int i = 0, max = segments_.length; i < max; i++) {
+            String segment = segments_[i];
+            if (segment.charAt(0) is '.' && (segment.equals("..") || segment.equals("."))) { //$NON-NLS-1$ //$NON-NLS-2$
+                //path needs to be canonicalized
+                collapseParentReferences();
+                //paths of length 0 have no trailing separator
+                if (segments_.length is 0)
+                    separators &= (HAS_LEADING | IS_UNC);
+                //recompute hash because canonicalize affects hash
+                separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Clones this object.
+     */
+    public Path clone() {
+        return new Path(device, segments_, separators);
+    }
+
+    /**
+     * Destructively removes all occurrences of ".." segments from this path.
+     */
+    private void collapseParentReferences() {
+        int segmentCount = segments_.length;
+        String[] stack = new String[segmentCount];
+        int stackPointer = 0;
+        for (int i = 0; i < segmentCount; i++) {
+            String segment = segments_[i];
+            if (segment.equals("..")) { //$NON-NLS-1$
+                if (stackPointer is 0) {
+                    // if the stack is empty we are going out of our scope
+                    // so we need to accumulate segments.  But only if the original
+                    // path is relative.  If it is absolute then we can't go any higher than
+                    // root so simply toss the .. references.
+                    if (!isAbsolute())
+                        stack[stackPointer++] = segment; //stack push
+                } else {
+                    // if the top is '..' then we are accumulating segments so don't pop
+                    if ("..".equals(stack[stackPointer - 1])) //$NON-NLS-1$
+                        stack[stackPointer++] = ".."; //$NON-NLS-1$
+                    else
+                        stackPointer--;
+                    //stack pop
+                }
+                //collapse current references
+            } else if (!segment.equals(".") || segmentCount is 1) //$NON-NLS-1$
+                stack[stackPointer++] = segment; //stack push
+        }
+        //if the number of segments hasn't changed, then no modification needed
+        if (stackPointer is segmentCount)
+            return;
+        //build the new segment array backwards by popping the stack
+        String[] newSegments = new String[stackPointer];
+        System.arraycopy(stack, 0, newSegments, 0, stackPointer);
+        this.segments_ = newSegments;
+    }
+
+    /**
+     * Removes duplicate slashes from the given path, with the exception
+     * of leading double slash which represents a UNC path.
+     */
+    private String collapseSlashes(String path) {
+        int length = path.length;
+        // if the path is only 0, 1 or 2 chars long then it could not possibly have illegal
+        // duplicate slashes.
+        if (length < 3)
+            return path;
+        // check for an occurrence of // in the path.  Start at index 1 to ensure we skip leading UNC //
+        // If there are no // then there is nothing to collapse so just return.
+        if (path.indexOf("//", 1) is -1) //$NON-NLS-1$
+            return path;
+        // We found an occurrence of // in the path so do the slow collapse.
+        char[] result = new char[path.length];
+        int count = 0;
+        bool hasPrevious = false;
+        char[] characters = path/+.toCharArray()+/;
+        for (int index = 0; index < characters.length; index++) {
+            char c = characters[index];
+            if (c is SEPARATOR) {
+                if (hasPrevious) {
+                    // skip double slashes, except for beginning of UNC.
+                    // note that a UNC path can't have a device.
+                    if (device is null && index is 1) {
+                        result[count] = c;
+                        count++;
+                    }
+                } else {
+                    hasPrevious = true;
+                    result[count] = c;
+                    count++;
+                }
+            } else {
+                hasPrevious = false;
+                result[count] = c;
+                count++;
+            }
+        }
+        return result[ 0 .. count];
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Computes the hash code for this object.
+     */
+    private int computeHashCode() {
+        int hash = device.length is 0 ? 17 : java.lang.all.toHash(device);
+        int segmentCount = segments_.length;
+        for (int i = 0; i < segmentCount; i++) {
+            //this function tends to given a fairly even distribution
+            hash = hash * 37 + java.lang.all.toHash(segments_[i]);
+        }
+        return hash;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Returns the size of the string that will be created by toString or toOSString.
+     */
+    private int computeLength() {
+        int length = 0;
+        if (device !is null)
+            length += device.length;
+        if ((separators & HAS_LEADING) !is 0)
+            length++;
+        if ((separators & IS_UNC) !is 0)
+            length++;
+        //add the segment lengths
+        int max = segments_.length;
+        if (max > 0) {
+            for (int i = 0; i < max; i++) {
+                length += segments_[i].length;
+            }
+            //add the separator lengths
+            length += max - 1;
+        }
+        if ((separators & HAS_TRAILING) !is 0)
+            length++;
+        return length;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Returns the number of segments in the given path
+     */
+    private int computeSegmentCount(String path) {
+        int len = path.length;
+        if (len is 0 || (len is 1 && path.charAt(0) is SEPARATOR)) {
+            return 0;
+        }
+        int count = 1;
+        int prev = -1;
+        int i;
+        while ((i = path.indexOf(SEPARATOR, prev + 1)) !is -1) {
+            if (i !is prev + 1 && i !is len) {
+                ++count;
+            }
+            prev = i;
+        }
+        if (path.charAt(len - 1) is SEPARATOR) {
+            --count;
+        }
+        return count;
+    }
+
+    /**
+     * Computes the segment array for the given canonicalized path.
+     */
+    private String[] computeSegments(String path) {
+        // performance sensitive --- avoid creating garbage
+        int segmentCount = computeSegmentCount(path);
+        if (segmentCount is 0)
+            return NO_SEGMENTS;
+        String[] newSegments = new String[segmentCount];
+        int len = path.length;
+        // check for initial slash
+        int firstPosition = (path.charAt(0) is SEPARATOR) ? 1 : 0;
+        // check for UNC
+        if (firstPosition is 1 && len > 1 && (path.charAt(1) is SEPARATOR))
+            firstPosition = 2;
+        int lastPosition = (path.charAt(len - 1) !is SEPARATOR) ? len - 1 : len - 2;
+        // for non-empty paths, the number of segments is
+        // the number of slashes plus 1, ignoring any leading
+        // and trailing slashes
+        int next = firstPosition;
+        for (int i = 0; i < segmentCount; i++) {
+            int start = next;
+            int end = path.indexOf(SEPARATOR, next);
+            if (end is -1) {
+                newSegments[i] = path.substring(start, lastPosition + 1);
+            } else {
+                newSegments[i] = path.substring(start, end);
+            }
+            next = end + 1;
+        }
+        return newSegments;
+    }
+
+    /**
+     * Returns the platform-neutral encoding of the given segment onto
+     * the given string buffer. This escapes literal colon characters with double colons.
+     */
+    private void encodeSegment(String string, StringBuffer buf) {
+        int len = string.length;
+        for (int i = 0; i < len; i++) {
+            char c = string.charAt(i);
+            buf.append(c);
+            if (c is DEVICE_SEPARATOR)
+                buf.append(DEVICE_SEPARATOR);
+        }
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Compares objects for equality.
+     */
+    public override int opEquals(Object obj) {
+        if (this is obj)
+            return true;
+        if (!(cast(Path)obj))
+            return false;
+        Path target = cast(Path) obj;
+        //check leading separators and hash code
+        if ((separators & HASH_MASK) !is (target.separators & HASH_MASK))
+            return false;
+        String[] targetSegments = target.segments_;
+        int i = segments_.length;
+        //check segment count
+        if (i !is targetSegments.length)
+            return false;
+        //check segments in reverse order - later segments more likely to differ
+        while (--i >= 0)
+            if (!segments_[i].equals(targetSegments[i]))
+                return false;
+        //check device last (least likely to differ)
+        return device is target.device || (device !is null && device.equals(target.device));
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#getDevice
+     */
+    public String getDevice() {
+        return device;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#getFileExtension
+     */
+    public String getFileExtension() {
+        if (hasTrailingSeparator()) {
+            return null;
+        }
+        String lastSegment = lastSegment();
+        if (lastSegment is null) {
+            return null;
+        }
+        int index = lastSegment.lastIndexOf('.');
+        if (index is -1) {
+            return null;
+        }
+        return lastSegment.substring(index + 1);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * Computes the hash code for this object.
+     */
+    public override hash_t toHash() {
+        return separators & HASH_MASK;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#hasTrailingSeparator2
+     */
+    public bool hasTrailingSeparator() {
+        return (separators & HAS_TRAILING) !is 0;
+    }
+
+    /*
+     * Initialize the current path with the given string.
+     */
+    private IPath initialize(String deviceString, String path) {
+        //Assert.isNotNull(path); // allow for SWT
+        this.device = deviceString;
+
+        path = collapseSlashes(path);
+        int len = path.length;
+
+        //compute the separators array
+        if (len < 2) {
+            if (len is 1 && path.charAt(0) is SEPARATOR) {
+                this.separators = HAS_LEADING;
+            } else {
+                this.separators = 0;
+            }
+        } else {
+            bool hasLeading = path.charAt(0) is SEPARATOR;
+            bool isUNC = hasLeading && path.charAt(1) is SEPARATOR;
+            //UNC path of length two has no trailing separator
+            bool hasTrailing = !(isUNC && len is 2) && path.charAt(len - 1) is SEPARATOR;
+            separators = hasLeading ? HAS_LEADING : 0;
+            if (isUNC)
+                separators |= IS_UNC;
+            if (hasTrailing)
+                separators |= HAS_TRAILING;
+        }
+        //compute segments and ensure canonical form
+        segments_ = computeSegments(path);
+        if (!canonicalize()) {
+            //compute hash now because canonicalize didn't need to do it
+            separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3);
+        }
+        return this;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#isAbsolute
+     */
+    public bool isAbsolute() {
+        //it's absolute if it has a leading separator
+        return (separators & HAS_LEADING) !is 0;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#isEmpty
+     */
+    public bool isEmpty() {
+        //true if no segments and no leading prefix
+        return segments_.length is 0 && ((separators & ALL_SEPARATORS) !is HAS_LEADING);
+
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#isPrefixOf
+     */
+    public bool isPrefixOf(IPath anotherPath) {
+        if (device is null) {
+            if (anotherPath.getDevice() !is null) {
+                return false;
+            }
+        } else {
+            if (!device.equalsIgnoreCase(anotherPath.getDevice())) {
+                return false;
+            }
+        }
+        if (isEmpty() || (isRoot() && anotherPath.isAbsolute())) {
+            return true;
+        }
+        int len = segments_.length;
+        if (len > anotherPath.segmentCount()) {
+            return false;
+        }
+        for (int i = 0; i < len; i++) {
+            if (!segments_[i].equals(anotherPath.segment(i)))
+                return false;
+        }
+        return true;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#isRoot
+     */
+    public bool isRoot() {
+        //must have no segments, a leading separator, and not be a UNC path.
+        return this is ROOT || (segments_.length is 0 && ((separators & ALL_SEPARATORS) is HAS_LEADING));
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#isUNC
+     */
+    public bool isUNC() {
+        if (device !is null)
+            return false;
+        return (separators & IS_UNC) !is 0;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#isValidPath(String)
+     */
+    public bool isValidPath(String path) {
+        Path test = new Path(path);
+        for (int i = 0, max = test.segmentCount(); i < max; i++)
+            if (!isValidSegment(test.segment(i)))
+                return false;
+        return true;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#isValidSegment(String)
+     */
+    public bool isValidSegment(String segment) {
+        int size = segment.length;
+        if (size is 0)
+            return false;
+        for (int i = 0; i < size; i++) {
+            char c = segment.charAt(i);
+            if (c is '/')
+                return false;
+            if (WINDOWS && (c is '\\' || c is ':'))
+                return false;
+        }
+        return true;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#lastSegment()
+     */
+    public String lastSegment() {
+        int len = segments_.length;
+        return len is 0 ? null : segments_[len - 1];
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#makeAbsolute()
+     */
+    public IPath makeAbsolute() {
+        if (isAbsolute()) {
+            return this;
+        }
+        Path result = new Path(device, segments_, separators | HAS_LEADING);
+        //may need canonicalizing if it has leading ".." or "." segments
+        if (result.segmentCount() > 0) {
+            String first = result.segment(0);
+            if (first.equals("..") || first.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$
+                result.canonicalize();
+            }
+        }
+        return result;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#makeRelative()
+     */
+    public IPath makeRelative() {
+        if (!isAbsolute()) {
+            return this;
+        }
+        return new Path(device, segments_, separators & HAS_TRAILING);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#makeUNC(bool)
+     */
+    public IPath makeUNC(bool toUNC) {
+        // if we are already in the right form then just return
+        if (!(toUNC ^ isUNC()))
+            return this;
+
+        int newSeparators = this.separators;
+        if (toUNC) {
+            newSeparators |= HAS_LEADING | IS_UNC;
+        } else {
+            //mask out the UNC bit
+            newSeparators &= HAS_LEADING | HAS_TRAILING;
+        }
+        return new Path(toUNC ? null : device, segments_, newSeparators);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#matchingFirstSegments(IPath)
+     */
+    public int matchingFirstSegments(IPath anotherPath) {
+        Assert.isNotNull( cast(Object) anotherPath);
+        int anotherPathLen = anotherPath.segmentCount();
+        int max = Math.min(segments_.length, anotherPathLen);
+        int count = 0;
+        for (int i = 0; i < max; i++) {
+            if (!segments_[i].equals(anotherPath.segment(i))) {
+                return count;
+            }
+            count++;
+        }
+        return count;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#removeFileExtension()
+     */
+    public IPath removeFileExtension() {
+        String extension = getFileExtension();
+        if (extension is null || extension.equals("")) { //$NON-NLS-1$
+            return this;
+        }
+        String lastSegment = lastSegment();
+        int index = lastSegment.lastIndexOf(extension) - 1;
+        return removeLastSegments(1).append(lastSegment.substring(0, index));
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#removeFirstSegments(int)
+     */
+    public IPath removeFirstSegments(int count) {
+        if (count is 0)
+            return this;
+        if (count >= segments_.length) {
+            return new Path(device, NO_SEGMENTS, 0);
+        }
+        Assert.isLegal(count > 0);
+        int newSize = segments_.length - count;
+        String[] newSegments = new String[newSize];
+        System.arraycopy(this.segments_, count, newSegments, 0, newSize);
+
+        //result is always a relative path
+        return new Path(device, newSegments, separators & HAS_TRAILING);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#removeLastSegments(int)
+     */
+    public IPath removeLastSegments(int count) {
+        if (count is 0)
+            return this;
+        if (count >= segments_.length) {
+            //result will have no trailing separator
+            return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC));
+        }
+        Assert.isLegal(count > 0);
+        int newSize = segments_.length - count;
+        String[] newSegments = new String[newSize];
+        System.arraycopy(this.segments_, 0, newSegments, 0, newSize);
+        return new Path(device, newSegments, separators);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#removeTrailingSeparator()
+     */
+    public IPath removeTrailingSeparator() {
+        if (!hasTrailingSeparator()) {
+            return this;
+        }
+        return new Path(device, segments_, separators & (HAS_LEADING | IS_UNC));
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#segment(int)
+     */
+    public String segment(int index) {
+        if (index >= segments_.length)
+            return null;
+        return segments_[index];
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#segmentCount()
+     */
+    public int segmentCount() {
+        return segments_.length;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#segments()
+     */
+    public String[] segments() {
+        String[] segmentCopy = new String[](segments_.length);
+        System.arraycopy(segments_, 0, segmentCopy, 0, segments_.length);
+        return segmentCopy;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#setDevice(String)
+     */
+    public IPath setDevice(String value) {
+        if (value !is null) {
+            Assert.isTrue(value.indexOf(IPath.DEVICE_SEPARATOR) is (value.length - 1), "Last character should be the device separator"); //$NON-NLS-1$
+        }
+        //return the receiver if the device is the same
+        if (value is device || (value !is null && value.equals(device)))
+            return this;
+
+        return new Path(value, segments_, separators);
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#toFile()
+     */
+    public FilePath toFile() {
+        return new FilePath(tango.io.Path.standard(toOSString()));
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#toOSString()
+     */
+    public String toOSString() {
+        //Note that this method is identical to toString except
+        //it uses the OS file separator instead of the path separator
+        int resultSize = computeLength();
+        if (resultSize <= 0)
+            return EMPTY_STRING;
+        char FILE_SEPARATOR = FileConst.PathSeparatorChar;
+        char[] result = new char[resultSize];
+        int offset = 0;
+        if (device !is null) {
+            int size = device.length;
+            device.getChars(0, size, result, offset);
+            offset += size;
+        }
+        if ((separators & HAS_LEADING) !is 0)
+            result[offset++] = FILE_SEPARATOR;
+        if ((separators & IS_UNC) !is 0)
+            result[offset++] = FILE_SEPARATOR;
+        int len = segments_.length - 1;
+        if (len >= 0) {
+            //append all but the last segment, with separators
+            for (int i = 0; i < len; i++) {
+                int size = segments_[i].length;
+                segments_[i].getChars(0, size, result, offset);
+                offset += size;
+                result[offset++] = FILE_SEPARATOR;
+            }
+            //append the last segment
+            int size = segments_[len].length;
+            segments_[len].getChars(0, size, result, offset);
+            offset += size;
+        }
+        if ((separators & HAS_TRAILING) !is 0)
+            result[offset++] = FILE_SEPARATOR;
+        return result;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#toPortableString()
+     */
+    public String toPortableString() {
+        int resultSize = computeLength();
+        if (resultSize <= 0)
+            return EMPTY_STRING;
+        StringBuffer result = new StringBuffer(resultSize);
+        if (device !is null)
+            result.append(device);
+        if ((separators & HAS_LEADING) !is 0)
+            result.append(SEPARATOR);
+        if ((separators & IS_UNC) !is 0)
+            result.append(SEPARATOR);
+        int len = segments_.length;
+        //append all segments with separators
+        for (int i = 0; i < len; i++) {
+            if (segments_[i].indexOf(DEVICE_SEPARATOR) >= 0)
+                encodeSegment(segments_[i], result);
+            else
+                result.append(segments_[i]);
+            if (i < len - 1 || (separators & HAS_TRAILING) !is 0)
+                result.append(SEPARATOR);
+        }
+        return result.toString();
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#toString()
+     */
+    public override String toString() {
+        int resultSize = computeLength();
+        if (resultSize <= 0)
+            return EMPTY_STRING;
+        char[] result = new char[resultSize];
+        int offset = 0;
+        if (device !is null) {
+            int size = device.length;
+            device.getChars(0, size, result, offset);
+            offset += size;
+        }
+        if ((separators & HAS_LEADING) !is 0)
+            result[offset++] = SEPARATOR;
+        if ((separators & IS_UNC) !is 0)
+            result[offset++] = SEPARATOR;
+        int len = segments_.length - 1;
+        if (len >= 0) {
+            //append all but the last segment, with separators
+            for (int i = 0; i < len; i++) {
+                int size = segments_[i].length;
+                segments_[i].getChars(0, size, result, offset);
+                offset += size;
+                result[offset++] = SEPARATOR;
+            }
+            //append the last segment
+            int size = segments_[len].length;
+            segments_[len].getChars(0, size, result, offset);
+            offset += size;
+        }
+        if ((separators & HAS_TRAILING) !is 0)
+            result[offset++] = SEPARATOR;
+        return result;
+    }
+
+    /* (Intentionally not included in javadoc)
+     * @see IPath#uptoSegment(int)
+     */
+    public IPath uptoSegment(int count) {
+        if (count is 0)
+            return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC));
+        if (count >= segments_.length)
+            return this;
+        Assert.isTrue(count > 0, "Invalid parameter to Path.uptoSegment"); //$NON-NLS-1$
+        String[] newSegments = new String[count];
+        System.arraycopy(segments_, 0, newSegments, 0, count);
+        return new Path(device, newSegments, separators);
+    }
+}