001: /*
002: * Copyright (C) 2004, 2005 Joe Walnes.
003: * Copyright (C) 2006, 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 02. September 2004 by Joe Walnes
011: */
012: package com.thoughtworks.xstream.io.path;
013:
014: import com.thoughtworks.xstream.core.util.FastStack;
015:
016: import java.util.ArrayList;
017: import java.util.List;
018:
019: /**
020: * Represents a path (subset of XPath) to a single node in the tree.
021: *
022: * <p>Two absolute paths can also be compared to calculate the relative path between them.
023: * A relative path can be applied to an absolute path to calculate another absolute path.</p>
024: *
025: * <p>Note that the paths produced are XPath compliant, so can be read by other XPath engines.
026: * The following are examples of path expressions that the Path object supports:</p>
027: * <ul>
028: * <li>/</li>
029: * <li>/some/node</li>
030: * <li>/a/b/c/b/a</li>
031: * <li>/some[3]/node[2]/a</li>
032: * <li>../../../another[3]/node</li>
033: * </ul>
034: *
035: * <h3>Example<h3>
036: *
037: * <pre>
038: * Path a = new Path("/html/body/div/table[2]/tr[3]/td/div");
039: * Path b = new Path("/html/body/div/table[2]/tr[6]/td/form");
040: *
041: * Path relativePath = a.relativeTo(b); // produces: "../../../tr[6]/td/form"
042: * Path c = a.apply(relativePath); // same as Path b.
043: * </pre>
044: *
045: * @see PathTracker
046: *
047: * @author Joe Walnes
048: */
049: public class Path {
050:
051: private final String[] chunks;
052: private transient String pathAsString;
053: private static final Path DOT = new Path(new String[] { "." });
054:
055: public Path(String pathAsString) {
056: // String.split() too slow. StringTokenizer too crappy.
057: List result = new ArrayList();
058: int currentIndex = 0;
059: int nextSeperator;
060: while ((nextSeperator = pathAsString.indexOf('/', currentIndex)) != -1) {
061: result.add(pathAsString.substring(currentIndex,
062: nextSeperator));
063: currentIndex = nextSeperator + 1;
064: }
065: result.add(pathAsString.substring(currentIndex));
066: String[] arr = new String[result.size()];
067: result.toArray(arr);
068: chunks = arr;
069: this .pathAsString = pathAsString;
070: }
071:
072: public Path(String[] chunks) {
073: this .chunks = chunks;
074: }
075:
076: public String toString() {
077: if (pathAsString == null) {
078: StringBuffer buffer = new StringBuffer();
079: for (int i = 0; i < chunks.length; i++) {
080: if (i > 0)
081: buffer.append('/');
082: buffer.append(chunks[i]);
083: }
084: pathAsString = buffer.toString();
085: }
086: return pathAsString;
087: }
088:
089: public boolean equals(Object o) {
090: if (this == o)
091: return true;
092: if (!(o instanceof Path))
093: return false;
094:
095: final Path other = (Path) o;
096: if (chunks.length != other.chunks.length)
097: return false;
098: for (int i = 0; i < chunks.length; i++) {
099: if (!chunks[i].equals(other.chunks[i]))
100: return false;
101: }
102:
103: return true;
104: }
105:
106: public int hashCode() {
107: int result = 543645643;
108: for (int i = 0; i < chunks.length; i++) {
109: result = 29 * result + chunks[i].hashCode();
110: }
111: return result;
112: }
113:
114: public Path relativeTo(Path that) {
115: int depthOfPathDivergence = depthOfPathDivergence(chunks,
116: that.chunks);
117: String[] result = new String[chunks.length + that.chunks.length
118: - 2 * depthOfPathDivergence];
119: int count = 0;
120:
121: for (int i = depthOfPathDivergence; i < chunks.length; i++) {
122: result[count++] = "..";
123: }
124: for (int j = depthOfPathDivergence; j < that.chunks.length; j++) {
125: result[count++] = that.chunks[j];
126: }
127:
128: if (count == 0) {
129: return DOT;
130: } else {
131: return new Path(result);
132: }
133: }
134:
135: private int depthOfPathDivergence(String[] path1, String[] path2) {
136: int minLength = Math.min(path1.length, path2.length);
137: for (int i = 0; i < minLength; i++) {
138: if (!path1[i].equals(path2[i])) {
139: return i;
140: }
141: }
142: return minLength;
143: }
144:
145: public Path apply(Path relativePath) {
146: FastStack absoluteStack = new FastStack(16);
147:
148: for (int i = 0; i < chunks.length; i++) {
149: absoluteStack.push(chunks[i]);
150: }
151:
152: for (int i = 0; i < relativePath.chunks.length; i++) {
153: String relativeChunk = relativePath.chunks[i];
154: if (relativeChunk.equals("..")) {
155: absoluteStack.pop();
156: } else if (!relativeChunk.equals(".")) {
157: absoluteStack.push(relativeChunk);
158: }
159: }
160:
161: String[] result = new String[absoluteStack.size()];
162: for (int i = 0; i < result.length; i++) {
163: result[i] = (String) absoluteStack.get(i);
164: }
165:
166: return new Path(result);
167: }
168:
169: public boolean isAncestor(Path child) {
170: if (child == null || child.chunks.length < chunks.length) {
171: return false;
172: }
173: for (int i = 0; i < chunks.length; i++) {
174: if (!chunks[i].equals(child.chunks[i])) {
175: return false;
176: }
177: }
178: return true;
179: }
180: }
|