KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > enhydra > jawe > xml > Path


1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 // Modified by Sasa Bojanic
12
package org.enhydra.jawe.xml;
13
14 import java.io.File JavaDoc;
15
16 /**
17  * Paths are always maintained in canonicalized form. That is, parent
18  * references (i.e., <code>../../</code>) and duplicate separators are
19  * resolved. For example,
20  * <pre> new Path("/a/b").append("../foo/bar")</pre>
21  * will yield the path
22  * <pre> /a/foo/bar</pre>
23  * <p>
24  * This class is not intended to be subclassed by clients but
25  * may be instantiated.
26  * </p>
27  */

28 public class Path {
29    /**
30     * Path separator character constant "/" used in paths.
31     */

32    public static final char SEPARATOR = '/';
33
34    /**
35     * Device separator character constant ":" used in paths.
36     */

37    public static final char DEVICE_SEPARATOR = ':';
38
39    /** The path segments */
40    private String JavaDoc[] segments;
41
42    /** The device id string. May be null if there is no device. */
43    private String JavaDoc device = null;
44
45    /** flags indicating separators (has leading, is UNC, has trailing) */
46    private int separators;
47
48    /** masks for separator values */
49    private static final int HAS_LEADING = 1;
50    private static final int IS_UNC = 2;
51    private static final int HAS_TRAILING = 4;
52    private static final int ALL_SEPARATORS = HAS_LEADING | IS_UNC | HAS_TRAILING;
53
54    /** Mask for all bits that are involved in the hashcode */
55    private static final int HASH_MASK = ~HAS_TRAILING;
56
57    /** Constant value indicating no segments */
58    private static final String JavaDoc[] NO_SEGMENTS = new String JavaDoc[0];
59
60    /** Constant root path string (<code>"/"</code>). */
61    private static final String JavaDoc ROOT_STRING = "/"; //$NON-NLS-1$
62

63    /** Constant value containing the root path with no device. */
64    public static final Path ROOT = new Path(ROOT_STRING);
65
66    /** Constant empty string value. */
67    private static final String JavaDoc EMPTY_STRING = ""; //$NON-NLS-1$
68

69    /** Constant value containing the empty path with no device. */
70    public static final Path EMPTY = new Path(EMPTY_STRING);
71
72    //Private implementation note: the segments and separators
73
//arrays are never modified, so that they can be shared between
74
//path instances
75
private Path(String JavaDoc device, String JavaDoc[] segments, int _separators) {
76       // no segment validations are done for performance reasons
77
this.segments = segments;
78       this.device = device;
79       //hashcode is cached in all but the bottom three bits of the separators field
80
this.separators = (computeHashCode() << 3) | (_separators & ALL_SEPARATORS);
81    }
82
83    /**
84     * Constructs a new path from the given string path.
85     * The given string path must be valid.
86     * The path is canonicalized and double slashes are removed
87     * except at the beginning. (to handle UNC paths) All backslashes ('\')
88     * are replaced with forward slashes. ('/')
89     *
90     * @param fullPath the string path
91     */

92    public Path(String JavaDoc fullPath) {
93       // no segment validations are done for performance reasons
94
initialize(null, fullPath);
95    }
96
97    /**
98     * Constructs a new path from the given device id and string path.
99     * The given string path must be valid.
100     * The path is canonicalized and double slashes are removed except
101     * at the beginning (to handle UNC paths). All backslashes ('\')
102     * are replaced with forward slashes. ('/')
103     *
104     * @param device the device id
105     * @param path the string path
106     * @see #setDevice
107     */

108    public Path(String JavaDoc device, String JavaDoc path) {
109       // no segment validations are done for performance reasons
110
initialize(device, path);
111    }
112
113    /**
114     * Destructively converts this path to its canonical form.
115     * <p>
116     * In its canonical form, a path does not have any
117     * "." segments, and parent references ("..") are collapsed
118     * where possible.
119     * </p>
120     * @return true if the path was modified, and false otherwise.
121     */

122    private boolean canonicalize() {
123       //look for segments that need canonicalizing
124
for (int i = 0, max = segments.length; i < max; i++) {
125          String JavaDoc segment = segments[i];
126          if (segment.charAt(0) == '.' && (segment.equals("..") || segment.equals("."))) { //$NON-NLS-1$ //$NON-NLS-2$
127
//path needs to be canonicalized
128
collapseParentReferences();
129             //paths of length 0 have no trailing separator
130
if (segments.length == 0)
131                separators &= (HAS_LEADING | IS_UNC);
132             //recompute hash because canonicalize affects hash
133
separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3);
134             return true;
135          }
136       }
137       return false;
138    }
139
140    /**
141     * Destructively removes all occurrences of ".." segments from this path.
142     */

143    private void collapseParentReferences() {
144       int segmentCount = segments.length;
145       String JavaDoc[] stack = new String JavaDoc[segmentCount];
146       int stackPointer = 0;
147       for (int i = 0; i < segmentCount; i++) {
148          String JavaDoc segment = segments[i];
149          if (segment.equals("..")) { //$NON-NLS-1$
150
if (stackPointer==0) {
151                // if the stack is empty we are going out of our scope
152
// so we need to accumulate segments. But only if the original
153
// path is relative. If it is absolute then we can't go any higher than
154
// root so simply toss the .. references.
155
if (!isAbsolute())
156                   stack[stackPointer++] = segment;//stack push
157
} else {
158                // if the top is '..' then we are accumulating segments so don't pop
159
if ("..".equals(stack[stackPointer-1])) //$NON-NLS-1$
160
stack[stackPointer++] = ".."; //$NON-NLS-1$
161
else
162                   stackPointer--;//stack pop
163
}
164             //collapse current references
165
} else
166             if (!segment.equals(".") || (i == 0 && !isAbsolute())) //$NON-NLS-1$
167
stack[stackPointer++] = segment;//stack push
168
}
169       //if the number of segments hasn't changed, then no modification needed
170
if (stackPointer== segmentCount)
171          return;
172       //build the new segment array backwards by popping the stack
173
String JavaDoc[] newSegments = new String JavaDoc[stackPointer];
174       System.arraycopy(stack, 0, newSegments, 0, stackPointer);
175       this.segments = newSegments;
176    }
177
178    /**
179     * Removes duplicate slashes from the given path, with the exception
180     * of leading double slash which represents a UNC path.
181     */

182    private String JavaDoc collapseSlashes(String JavaDoc path) {
183       int length = path.length();
184       // if the path is only 0, 1 or 2 chars long then it could not possibly have illegal
185
// duplicate slashes.
186
if (length < 3)
187          return path;
188       // check for an occurence of // in the path. Start at index 1 to ensure we skip leading UNC //
189
// If there are no // then there is nothing to collapse so just return.
190
if (path.indexOf("//", 1) == -1) //$NON-NLS-1$
191
return path;
192       // We found an occurence of // in the path so do the slow collapse.
193
char[] result = new char[path.length()];
194       int count = 0;
195       boolean hasPrevious = false;
196       char[] characters = path.toCharArray();
197       for (int index = 0; index < characters.length; index++) {
198          char c = characters[index];
199          if (c == SEPARATOR) {
200             if (hasPrevious) {
201                // skip double slashes, except for beginning of UNC.
202
// note that a UNC path can't have a device.
203
if (device == null && index == 1) {
204                   result[count] = c;
205                   count++;
206                }
207             } else {
208                hasPrevious = true;
209                result[count] = c;
210                count++;
211             }
212          } else {
213             hasPrevious = false;
214             result[count] = c;
215             count++;
216          }
217       }
218       return new String JavaDoc(result, 0, count);
219    }
220
221    /*
222    * Computes the hash code for this object.
223    */

224    private int computeHashCode() {
225       int hash = device == null ? 17 : device.hashCode();
226       int segmentCount = segments.length;
227       for (int i = 0; i < segmentCount; i++) {
228          //this function tends to given a fairly even distribution
229
hash = hash * 37 + segments[i].hashCode();
230       }
231       return hash;
232    }
233
234    /*
235    * Returns the size of the string that will be created by toString or toOSString.
236    */

237    private int computeLength() {
238       int length = 0;
239       if (device != null)
240          length += device.length();
241       if ((separators & HAS_LEADING) != 0)
242          length ++;
243       if ((separators & IS_UNC) != 0)
244          length++;
245       //add the segment lengths
246
int max = segments.length;
247       if (max > 0) {
248          for (int i = 0; i < max; i++) {
249             length += segments[i].length();
250          }
251          //add the separator lengths
252
length += max-1;
253       }
254       if ((separators & HAS_TRAILING) != 0)
255          length++;
256       return length;
257    }
258
259    /*
260    * Returns the number of segments in the given path
261    */

262    private int computeSegmentCount(String JavaDoc path) {
263       int len = path.length();
264       if (len == 0 || (len == 1 && path.charAt(0) == SEPARATOR)) {
265          return 0;
266       }
267       int count = 1;
268       int prev = -1;
269       int i;
270       while ((i = path.indexOf(SEPARATOR, prev + 1)) != -1) {
271          if (i != prev + 1 && i != len) {
272             ++count;
273          }
274          prev = i;
275       }
276       if (path.charAt(len - 1) == SEPARATOR) {
277          --count;
278       }
279       return count;
280    }
281
282    /**
283     * Computes the segment array for the given canonicalized path.
284     */

285    private String JavaDoc[] computeSegments(String JavaDoc path) {
286       // performance sensitive --- avoid creating garbage
287
int segmentCount = computeSegmentCount(path);
288       if (segmentCount == 0)
289          return NO_SEGMENTS;
290       String JavaDoc[] newSegments = new String JavaDoc[segmentCount];
291       int len = path.length();
292       // check for initial slash
293
int firstPosition = (path.charAt(0) == SEPARATOR) ? 1 : 0;
294       // check for UNC
295
if (firstPosition == 1 && len > 1 && (path.charAt(1) == SEPARATOR))
296          firstPosition = 2;
297       int lastPosition = (path.charAt(len - 1) != SEPARATOR) ? len - 1 : len - 2;
298       // for non-empty paths, the number of segments is
299
// the number of slashes plus 1, ignoring any leading
300
// and trailing slashes
301
int next = firstPosition;
302       for (int i = 0; i < segmentCount; i++) {
303          int start = next;
304          int end = path.indexOf(SEPARATOR, next);
305          if (end == -1) {
306             newSegments[i] = path.substring(start, lastPosition + 1);
307          } else {
308             newSegments[i] = path.substring(start, end);
309          }
310          next = end + 1;
311       }
312       return newSegments;
313    }
314
315
316    /*
317    * Initialize the current path with the given string.
318    */

319    private void initialize(String JavaDoc device, String JavaDoc fullPath) {
320       if (fullPath==null) throw new RuntimeException JavaDoc();
321       this.device = device;
322
323       //indexOf is much faster than replace
324
String JavaDoc path = fullPath.indexOf('\\') == -1 ? fullPath : fullPath.replace('\\', SEPARATOR);
325
326       int i = path.indexOf(DEVICE_SEPARATOR);
327       if (i != -1) {
328          // if the specified device is null then set it to
329
// be whatever is defined in the path string
330
if (device == null)
331             this.device = path.substring(0, i + 1);
332          path = path.substring(i + 1, path.length());
333       }
334       path = collapseSlashes(path);
335       int len = path.length();
336
337       //compute the separators array
338
if (len < 2) {
339          if (len == 1 && path.charAt(0) == SEPARATOR) {
340             this.separators = HAS_LEADING;
341          } else {
342             this.separators = 0;
343          }
344       } else {
345          boolean hasLeading = path.charAt(0) == SEPARATOR;
346          boolean isUNC = hasLeading && path.charAt(1) == SEPARATOR;
347          //UNC path of length two has no trailing separator
348
boolean hasTrailing = !(isUNC && len == 2) && path.charAt(len-1) == SEPARATOR;
349          separators = hasLeading ? HAS_LEADING : 0;
350          if (isUNC) separators |= IS_UNC;
351          if (hasTrailing) separators |= HAS_TRAILING;
352       }
353       //compute segments and ensure canonical form
354
segments = computeSegments(path);
355       if (!canonicalize()) {
356          //compute hash now because canonicalize didn't need to do it
357
separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3);
358       }
359    }
360
361    public boolean isAbsolute() {
362       //it's absolute if it has a leading separator
363
return (separators & HAS_LEADING) != 0;
364    }
365
366    public int matchingFirstSegments(Path anotherPath) {
367       if (anotherPath==null) throw new RuntimeException JavaDoc();
368       int anotherPathLen = anotherPath.segmentCount();
369       int max = Math.min(segments.length, anotherPathLen);
370       int count = 0;
371       for (int i = 0; i < max; i++) {
372          if (!segments[i].equals(anotherPath.segment(i))) {
373             return count;
374          }
375          count++;
376       }
377       return count;
378    }
379
380    public String JavaDoc segment(int index) {
381       if (index >= segments.length)
382          return null;
383       return segments[index];
384    }
385
386    public int segmentCount() {
387       return segments.length;
388    }
389
390    public String JavaDoc[] segments() {
391       String JavaDoc[] segmentCopy = new String JavaDoc[segments.length];
392       System.arraycopy(segments, 0, segmentCopy, 0, segments.length);
393       return segmentCopy;
394    }
395
396    public Path setDevice(String JavaDoc value) {
397       if (value != null) {
398          //Assert.isTrue(value.indexOf(Path.DEVICE_SEPARATOR) == (value.length() - 1), "Last character should be the device separator"); //$NON-NLS-1$
399
if (value.indexOf(Path.DEVICE_SEPARATOR) != (value.length() - 1)) throw new RuntimeException JavaDoc("Last character should be the device separator");
400       }
401       //return the reciever if the device is the same
402
if (value == device || (value != null && value.equals(device)))
403          return this;
404
405       return new Path(value, segments, separators);
406    }
407
408    public String JavaDoc toOSString() {
409       //Note that this method is identical to toString except
410
//it uses the OS file separator instead of the path separator
411
int resultSize = computeLength();
412       if (resultSize <= 0)
413          return EMPTY_STRING;
414       char FILE_SEPARATOR = File.separatorChar;
415       char[] result = new char[resultSize];
416       int offset = 0;
417       if (device != null) {
418          int size = device.length();
419          device.getChars(0, size, result, offset);
420          offset += size;
421       }
422       if ((separators & HAS_LEADING) != 0)
423          result[offset++] = FILE_SEPARATOR;
424       if ((separators & IS_UNC) != 0)
425          result[offset++] = FILE_SEPARATOR;
426       int len = segments.length-1;
427       if (len>=0) {
428          //append all but the last segment, with separators
429
for (int i = 0; i < len; i++) {
430             int size = segments[i].length();
431             segments[i].getChars(0, size, result, offset);
432             offset += size;
433             result[offset++] = FILE_SEPARATOR;
434          }
435          //append the last segment
436
int size = segments[len].length();
437          segments[len].getChars(0, size, result, offset);
438          offset += size;
439       }
440       if ((separators & HAS_TRAILING) != 0)
441          result[offset++] = FILE_SEPARATOR;
442       return new String JavaDoc(result);
443    }
444
445    public static String JavaDoc getRelativePath(Path fullPath,Path fBasePath) {
446       if (fBasePath == null || !hasSameDevice(fullPath, fBasePath)) {
447          return fullPath.toOSString();
448       }
449       int matchingSegments= fBasePath.matchingFirstSegments(fullPath);
450       StringBuffer JavaDoc res= new StringBuffer JavaDoc();
451       int backSegments= fBasePath.segmentCount() - matchingSegments;
452       while (backSegments > 0) {
453          res.append(".."); //$NON-NLS-1$
454
res.append(File.separatorChar);
455          backSegments--;
456       }
457       int segCount= fullPath.segmentCount();
458       for (int i= matchingSegments; i < segCount; i++) {
459          if (i > matchingSegments) {
460             res.append(File.separatorChar);
461          }
462          res.append(fullPath.segment(i));
463       }
464       return res.toString();
465    }
466
467    private static boolean hasSameDevice(Path p1, Path p2) {
468       String JavaDoc dev= p1.device;
469       if (dev == null) {
470          return p2.device == null;
471       }
472       return dev.equals(p2.device);
473    }
474
475 }
476
Popular Tags