Modified Path representation to use String[]
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..144b5bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+bin/
+*~
+*.rej
+*.bak
+core
+*.patch
+javacore.*
+heapdump.*
+core.*
+Snap.*
+target/
+
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/PathOld.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/PathOld.java
new file mode 100644
index 0000000..c4604e6
--- /dev/null
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/PathOld.java
@@ -0,0 +1,753 @@
+package org.eclipse.core.runtime;

+

+/*

+ * (c) Copyright IBM Corp. 2000, 2001.

+ * All Rights Reserved.

+ */

+import org.eclipse.core.internal.runtime.Assert;

+import java.util.*;

+import java.io.File;

+

+/** 

+ * 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 is not intended to be subclassed by clients but

+ * may be instantiated.

+ * </p>

+ * @see IPath

+ */

+public class PathOld implements IPath, Cloneable {

+	

+	/** The path string (never null). */

+	private String path = "";

+

+	/** The device id string. May be null if there is no device. */

+	private String device = null;

+

+	/** Constant root path string (<code>"/"</code>). */

+	private static final String ROOT_STRING = "/";

+

+	/** Constant value containing the root path with no device. */

+	public static final Path ROOT = new Path(ROOT_STRING);

+

+	/** Constant empty string value. */

+	private static final String EMPTY_STRING = "";

+

+	/** Constant value containing the empty path with no device. */

+	public static final Path EMPTY = new Path(EMPTY_STRING);

+/* (Intentionally not included in javadoc)

+ * Private constructor.

+ */

+private PathOld() {

+	

+	super();

+}

+

+/** 

+ * Constructs a new path from the given 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 backslashes ('\')

+ * are replaced with forward slashes. ('/')

+ *

+ * @param fullPath the string path

+ * @see #isValidPath

+ */

+public PathOld(String fullPath) {

+	super();

+	initialize(null, 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 backslashes ('\')

+ * are replaced with forward slashes. ('/')

+ *

+ * @param device the device id

+ * @param path the string path

+ * @see #isValidPath

+ * @see #setDevice

+ */

+public PathOld(String device, String path) {

+	super();

+	initialize(device, path);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#addFileExtension

+ */

+public IPath addFileExtension(String extension) {

+	if (isRoot() || isEmpty() || hasTrailingSeparator())

+		return this;

+	return new Path(device, path + "." + extension);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#addTrailingSeparator

+ */

+public IPath addTrailingSeparator() {

+	if (hasTrailingSeparator() || isRoot()) {

+		return this;

+	}

+	Path result = (Path) clone();

+	result.setPath(path + SEPARATOR);

+	return result;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#append(java.lang.String)

+ */

+public IPath append(String tail) {

+

+	// Appending a zero length string has no effect.

+	int tailLength = tail.length();

+	if (tailLength == 0)

+		return this;

+

+	// Must retain compatibility with zero path length appending

+	// so do a slow append.

+	int pathLength = path.length();

+	if (pathLength == 0)

+		return append(new Path(tail));

+

+	// Figure out the leading separator case for the tail

+	// and trailing separator for the receiver.

+	boolean trailing = path.charAt(path.length() - 1) == SEPARATOR;

+	boolean leading = tail.charAt(0) == SEPARATOR;

+

+	// If the receiver has a trailing separator *and* the tail

+	// has a leading separator, we have at least one separator too many.

+	if (leading && trailing)

+		return append(tail.substring(1, tailLength));

+

+	// If the receiver does not have a trailing separator and the

+	// the tail does not have a leading separator then we will

+	// actually be appending "/" + tail.

+	String append;

+	int appendLength;

+	if (!trailing && !leading) {

+		append = ROOT_STRING.concat(tail);

+		appendLength = tailLength + 1;

+	} else {

+		append = tail;

+		appendLength = tailLength;

+	}

+

+	// Fast track the case of appending a segment

+	// known not to have any effect on canonicalization.

+

+	// Scan the tail looking for instances of "//" or

+	// "/../" or "/./" which are canon-breakers.

+	for (int i = 0; i < appendLength - 1; i++) {

+		char c = append.charAt(i);

+		if ((c == SEPARATOR) || (c == '.')) {

+			char lookAhead = append.charAt(i + 1);

+			if ((lookAhead == SEPARATOR) || (lookAhead == '.'))

+				// Have to do the slow append with re-canonicalization.

+				return append(new Path(tail));

+		}

+	}

+

+	// This is a fast-track append.

+	Path newPath = new Path();

+	newPath.device = device;

+	newPath.path = path.concat(append);

+	return newPath;
+}

+

+/* (Intentionally not included in javadoc)

+ * @see IPath#append(IPath)

+ */

+public IPath append(IPath tail) {

+	if (tail == null || tail.isEmpty() || tail.isRoot()) {

+		return this;

+	}

+	String tailPathPart = ((Path) tail).getPathPart();

+	if (tailPathPart.startsWith("/")) {

+		tailPathPart = tailPathPart.substring(1);

+	}

+	String newPath = (isRoot() || isEmpty() || hasTrailingSeparator()) ? path.concat(tailPathPart) : path + SEPARATOR + tailPathPart;

+	Path result = (Path) clone();

+	result.setPath(newPath);

+	result.collapseSlashes();

+	if (tailPathPart.startsWith("..") || tailPathPart.startsWith("./")) {

+		result.canonicalize();

+	}

+	return result;

+}

+/**

+ * 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 the canonicalized path

+ */

+private void canonicalize() {

+	// Test to see if the receiver is already in canonical form.

+	if ((path.indexOf("/.") != -1) || (path.indexOf("./") != -1)) {

+		collapseParentReferences();

+	}

+}

+/* (Intentionally not included in javadoc)

+ * Clones this object.

+ */

+public Object clone() {

+	try {

+		return super.clone();

+	} catch (CloneNotSupportedException e) {

+		return null;

+	}

+}

+/**

+ * Destructively removes all occurrences of ".." segments from this path.

+ */

+private void collapseParentReferences() {

+	Stack stack = new Stack();

+	String[] segments = segments();

+	for (int i = 0; i < segments.length; i++) {

+		String segment = segments[i];

+		if (segment.equals("..")) {

+			if (stack.isEmpty()) {

+				// 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.push(segment);

+			} else {

+				// if the top is '..' then we are accumulating segments so don't pop

+				if (stack.peek().equals(".."))

+					stack.push("..");

+				else

+					stack.pop();

+			}

+			//collapse current references

+		} else

+			if (!segment.equals(".") || (i == 0 && !isAbsolute()))

+				stack.push(segment);

+	}

+	StringBuffer newPath = new StringBuffer(stack.isEmpty() ? "" : (String) stack.pop());

+	while (!stack.isEmpty()) {

+		newPath.insert(0, SEPARATOR);

+		newPath.insert(0, (String) stack.pop());

+	}

+	if (isAbsolute()) {

+		newPath.insert(0, SEPARATOR);

+	}

+	if (hasTrailingSeparator()) {

+		newPath.append(SEPARATOR);

+	}

+	path = newPath.toString();

+}

+/*

+ *

+ */

+private void collapseSlashes() {

+	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;

+	// check for an occurence 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) == -1)

+		return;

+	// We found an occurence of // in the path so do the slow collapse.

+	char[] result = new char[path.length()];

+	int count = 0;

+	boolean hasPrevious = false;

+	char[] characters = path.toCharArray();

+	for (int index = 0; index < characters.length; index++) {

+		char c = characters[index];

+		if (c == SEPARATOR) {

+			if (hasPrevious) {

+				// skip double slashes, except for beginning of UNC.

+				// note that a UNC path can't have a device.

+				if (device == null && index == 1) {

+					result[count] = c;

+					count++;

+				}

+			} else {

+				hasPrevious = true;

+				result[count] = c;

+				count++;

+			}

+		} else {

+			hasPrevious = false;

+			result[count] = c;

+			count++;

+		}

+	}

+	path = new String(result, 0, count);

+}

+/* (Intentionally not included in javadoc)

+ * Compares objects for equality.

+ */

+public boolean equals(Object obj) {

+	if (this == obj) {

+		return true;

+	}

+	if (!(obj instanceof IPath)) {

+		return false;

+	}

+	IPath target = (IPath) obj;

+	return removeTrailingSeparator().toString().equals(target.removeTrailingSeparator().toString());

+}

+

+/* (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 == null) {

+		return null;

+	}

+	int index = lastSegment.lastIndexOf(".");

+	if (index == -1) {

+		return null;

+	}

+	return lastSegment.substring(index + 1);

+}

+/**

+ * Returns the string representation of this path, without the device.

+ *

+ * @return the string path without the device

+ */

+private String getPathPart() {

+	return path;

+}

+/* (Intentionally not included in javadoc)

+ * Computes the hash code for this object.

+ */

+public int hashCode() {

+	return removeTrailingSeparator().toString().hashCode();

+}

+

+/* (Intentionally not included in javadoc)

+ * @see IPath#hasTrailingSeparator

+ */

+public boolean hasTrailingSeparator() {

+	int len = path.length();

+	// len > 1 because ROOT is not considered to have a trailing separator.

+	return len > 1 && path.charAt(len-1) == SEPARATOR;

+}

+/**

+ * Returns the index of the first character for the segment

+ * with the given index.

+ *

+ * @param index a 0-based segment index

+ * @return a 0-based character index

+ */

+private int indexOfSegment(int index) {

+	int len = path.length();

+	int i = 0;

+	while (i < len && path.charAt(i) == SEPARATOR) {

+		++i;

+	}

+	int j = index;

+	while (i < len && --j >= 0) {

+		i = path.indexOf(SEPARATOR, i + 1);

+		if (i == -1) {

+			return -1;

+		}

+		++i;

+	}

+	if (i < len) {

+		return i;

+	}

+	return -1;

+}

+/*

+ * Initialize the current path with the given string.

+ */

+private void initialize(String device, String fullPath) {

+	Assert.isNotNull(fullPath);

+	this.device = device;

+	path = fullPath.replace('\\', SEPARATOR);

+	int i = path.indexOf(DEVICE_SEPARATOR);

+	if (i != -1) {

+		// if the specified device is null then set it to

+		// be whatever is defined in the path string

+		if (device == null)

+			this.device = path.substring(0, i + 1);

+		path = path.substring(i + 1, path.length());

+	}

+	collapseSlashes();

+	canonicalize();

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#isAbsolute

+ */

+public boolean isAbsolute() {

+	return path.length() > 0 && (path.charAt(0) == SEPARATOR);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#isEmpty

+ */

+public boolean isEmpty() {

+	return path.length() == 0;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#isPrefixOf

+ */

+public boolean isPrefixOf(IPath anotherPath) {

+	Assert.isNotNull(anotherPath);

+	if (device == null) {

+		if (anotherPath.getDevice() != null) {

+			return false;

+		}

+	} else {

+		if (!device.equalsIgnoreCase(anotherPath.getDevice())) {

+			return false;

+		}

+	}

+	if (isEmpty() || (isRoot() && anotherPath.isAbsolute())) {

+		return true;

+	}

+	String possiblePrefix = hasTrailingSeparator() ? path.substring(0, path.length() - 1) : path;

+	String otherPath = ((Path) anotherPath).getPathPart();

+	if (!otherPath.startsWith(possiblePrefix)) {

+		return false;

+	}

+	return otherPath.length() == possiblePrefix.length() || otherPath.charAt(possiblePrefix.length()) == SEPARATOR;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#isRoot

+ */

+public boolean isRoot() {

+	return this == ROOT || (path.length() == 1 && path.charAt(0) == SEPARATOR);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#isUNC

+ */

+public boolean isUNC() {

+	if (device != null) 

+		return false;

+	if (path.length() < 2)

+		return false;

+	return path.charAt(0) == SEPARATOR && path.charAt(1) == SEPARATOR;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#isValidPath

+ */

+public boolean isValidPath(String path) {

+	// We allow "//" at the beginning for UNC paths

+	if (path.indexOf("//") > 0) {

+		return false;

+	}

+	Path test = new Path(path);

+	String[] segments = test.segments();

+	for (int i = 0; i < segments.length; i++) {

+		if (!test.isValidSegment(segments[i])) {

+			return false;

+		}

+	}

+	return true;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#isValidSegment

+ */

+public boolean isValidSegment(String segment) {

+	int size = segment.length();

+	if (size == 0) {

+		return false;

+	}

+	if (Character.isWhitespace(segment.charAt(0)) || Character.isWhitespace(segment.charAt(size - 1))) {

+		return false;

+	}

+	for (int i = 0; i < size; i++) {

+		char c = segment.charAt(i);

+		if (c == '/' || c == '\\' || c == ':') {

+			return false;

+		}

+	}

+	return true;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#lastSegment

+ */

+public String lastSegment() {

+	int end = path.length() - 1;

+	while (end >= 0 && path.charAt(end) == SEPARATOR) {

+		--end;

+	}

+	if (end < 0) {

+		return null;

+	}

+	int start = path.lastIndexOf(SEPARATOR, end - 1);

+	return path.substring((start == -1 ? 0 : start + 1), end + 1);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#makeAbsolute

+ */

+public IPath makeAbsolute() {

+	if (isAbsolute()) {

+		return this;

+	}

+	Path result = (Path) clone();

+	result.setPath(String.valueOf(SEPARATOR) + path);

+	return result;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#makeRelative

+ */

+public IPath makeRelative() {

+	if (!isAbsolute()) {

+		return this;

+	}

+	Path result = (Path) clone();

+	result.setPath(path.substring(1));

+	return result;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#makeUNC

+ */

+public IPath makeUNC(boolean toUNC) {

+	Path result = (Path) this.clone();

+

+	// if we are already in the right form then just return

+	if (!(toUNC ^ result.isUNC()))

+		return result;

+		

+	if (toUNC) {

+		result = (Path) result.setDevice(null);

+		String prefix = isAbsolute() ? String.valueOf(SEPARATOR) : String.valueOf(new char[] {SEPARATOR, SEPARATOR});

+		result.setPath(prefix.concat(result.getPathPart()));

+	} else {

+		// remove one of the leading slashes

+		result.setPath(path.substring(1, path.length()));

+	}

+	return result;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#matchingFirstSegments

+ */

+public int matchingFirstSegments(IPath anotherPath) {

+	Assert.isNotNull(anotherPath);

+	String[] local = segments();

+	String[] argument = anotherPath.segments();

+	int max = Math.min(local.length, argument.length);

+	int count = 0;

+	for (int i = 0; i < max; i++) {

+		if (!local[i].equals(argument[i])) {

+			return count;

+		}

+		count++;

+	}

+	return count;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#removeFileExtension

+ */

+public IPath removeFileExtension() {

+	String extension = getFileExtension();

+	if (extension == null || extension.equals("")) {

+		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

+ */

+public IPath removeFirstSegments(int count) {

+	if (count == 0) {

+		return this;

+	}

+	Assert.isLegal(count > 0);

+	int start = indexOfSegment(count);

+	Path result = (Path) clone();

+	if (start == -1) {

+		result.setPath(EMPTY_STRING);

+	} else {

+		result.setPath(path.substring(start));

+	}

+	return result;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#removeLastSegments

+ */

+public IPath removeLastSegments(int count) {

+	if (count == 0) {

+		return this;

+	}

+	Assert.isLegal(count > 0);

+	int index = hasTrailingSeparator() ? path.length() - 1 : path.length();

+	for (int i = 0; i < count; i++) {

+		index = path.lastIndexOf(SEPARATOR, index - 1);

+		if (index == -1) {

+			break;

+		}

+	}

+	Path result = (Path) clone();

+	if ((index == -1) || (index == 0)) {

+		result.setPath(isAbsolute() ? ROOT_STRING : EMPTY_STRING);

+		return result;

+	}

+	if (hasTrailingSeparator()) {

+		result.setPath(path.substring(0, index + 1));

+	} else {

+		result.setPath(path.substring(0, index));

+	}

+	return result;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#removeTrailingSeparator

+ */

+public IPath removeTrailingSeparator() {

+	if (!hasTrailingSeparator()) {

+		return this;

+	}

+	Path result = (Path) clone();

+	result.setPath(path.substring(0, path.length() - 1));

+	return result;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#segment

+ */

+public String segment(int index) {

+	int start = indexOfSegment(index);

+	if (start == -1) {

+		return null;

+	}

+	int end = path.indexOf(SEPARATOR, start + 1);

+	return end == -1 ? path.substring(start) : path.substring(start, end);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#segmentCount

+ */

+public int segmentCount() {

+	int len = path.length();

+	if (len == 0 || (len == 1 && path.charAt(0) == SEPARATOR)) {

+		return 0;

+	}

+	int count = 1;

+	int prev = -1;

+	int i;

+	while ((i = path.indexOf(SEPARATOR, prev + 1)) != -1) {

+		if (i != prev + 1 && i != len) {

+			++count;

+		}

+		prev = i;

+	}

+	if (path.charAt(len - 1) == SEPARATOR) {

+		--count;

+	}

+	return count;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#segments

+ */

+public String[] segments() {

+	// performance sensitive --- avoid creating garbage

+	int segmentCount = segmentCount();

+	String[] segments = new String[segmentCount];

+	if (segmentCount == 0) {

+		return new String[0];

+	}

+	int len = path.length();

+	// check for initial slash

+	int firstPosition = (path.charAt(0) == SEPARATOR) ? 1 : 0;

+	// check for UNC

+	if (firstPosition == 1 && isUNC())

+		firstPosition = 2;

+	int lastPosition = (path.charAt(len - 1) != 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 == -1) {

+			segments[i] = path.substring(start, lastPosition + 1);

+		} else {

+			segments[i] = path.substring(start, end);

+		}

+		next = end + 1;

+	}

+	return segments;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#setDevice

+ */

+public IPath setDevice(String value) {

+	if (value != null) {

+		Assert.isTrue(value.indexOf(IPath.DEVICE_SEPARATOR) == (value.length() - 1), "Last character should be the device separator");

+	}

+	Path result = (Path) clone();

+	result.device = value;

+	return result;

+}

+/**

+ * Destructively sets the path to the given string.

+ *

+ * @param value a string path

+ */

+private void setPath(String value) {

+	path = value;

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#toFile

+ */

+public File toFile() {

+	return new File(toOSString());

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#toOSString

+ */

+public String toOSString() {

+	String osPath = path.replace(SEPARATOR, File.separatorChar);

+	return device == null ? osPath : device.concat(osPath);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#toString

+ */

+public String toString() {

+	return device == null ? path : device.concat(path);

+}

+/* (Intentionally not included in javadoc)

+ * @see IPath#uptoSegment

+ */

+public IPath uptoSegment(int count) {

+	if (count == 0) {

+		return new Path("");

+	}

+	String source = hasTrailingSeparator() ? path.substring(0, path.length() - 1) : path;

+	int index = isAbsolute() ? 0 : -1;

+	for (int i = 0; i < count; i++) {

+		index = path.indexOf(SEPARATOR, index + 1);

+		if (index == -1) {

+			index = source.length();

+			break;

+		}

+	}

+	Path result = (Path) clone();

+	if ((index == -1) || (index == 0)) {

+		result.setPath(isAbsolute() ? ROOT_STRING : EMPTY_STRING);

+		return result;

+	}

+	if (hasTrailingSeparator()) {

+		result.setPath(path.substring(0, index + 1));

+	} else {

+		result.setPath(path.substring(0, index));

+	}

+	return result;

+}

+}