KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > meshcms > util > Path


1 /*
2  * MeshCMS - A simple CMS based on SiteMesh
3  * Copyright (C) 2004-2007 Luciano Vernaschi
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  *
19  * You can contact the author at http://www.cromoteca.com
20  * and at info@cromoteca.com
21  */

22
23 package org.meshcms.util;
24
25 import java.io.*;
26 import java.util.*;
27
28 /**
29  * An abstract representation of a file path. The root of the path is
30  * undefined, and the path can be relative (i.e. can start with '..').
31  *
32  * Example of paths are:
33  *
34  * <ul>
35  * <li>(the empty path)<br>This is meant as the (relative) root;</li>
36  * <li>filename.txt</li>
37  * <li>home/user/document.html</li>
38  * <li>../../directoryname</li>
39  * </ul>
40  *
41  * A <code>Path</code> can be created from any object. When you call a
42  * constructor, the path is initialized as empty, then the objects passed to
43  * the constructor are added to it.
44  *
45  * When all objects have been added, the path is simplified by removing
46  * redundant elements. For example, "home/user/../otheruser"
47  * is reduced to "home/otheruser".
48  *
49  * After the constructor returns, the
50  * <code>Path</code> object is immutable. When you call a method to modify
51  * it (like one of the <code>add()</code> methods), it returns a new
52  * <code>Path</code> that is the result of the requested operation.
53  *
54  * The objects are added as follows:
55  *
56  * <ul>
57  * <li>if the object is null, nothing is added;</li>
58  * <li>if it is another <code>Path</code>, its elements are added;</li>
59  * <li>if it is a <code>String</code>, it is split in tokens (divided by
60  * slashes or backslashes) and these tokens are added as elements;</li>
61  * <li>if it is a <code>Collection</code>, any member of the
62  * <code>Collection</code> is added as a separate object;</li>
63  * <li>if it is an array of <code>String</code>s, any member of the array is
64  * added as a separate <code>String</code> (to be tokenized);</li>
65  * <li>if it is another kind of object, its <code>toString()</code> method is
66  * called and the returned <code>String</code> is tokenized and added.</li>
67  * </ul>
68  *
69  * @author Luciano Vernaschi
70  */

71 public class Path implements Comparable JavaDoc, Serializable, Cloneable JavaDoc {
72   protected String JavaDoc pathName;
73   protected String JavaDoc[] elements;
74
75   public static final Path ROOT = new Path();
76
77   /**
78    * Creates an empty path.
79    */

80   public Path() {
81     this(null, null, null);
82   }
83
84   /**
85    * Creates a path and adds an object to it.
86    *
87    * @param o the Object to be added to this new path
88    */

89   public Path(Object JavaDoc o) {
90     this(o, null, null);
91   }
92
93   /**
94    * Creates a path and adds two objects to it.
95    *
96    * @param o1 the Object 1 to be added
97    * @param o2 the Object 2 to be added
98    */

99   public Path(Object JavaDoc o1, Object JavaDoc o2) {
100     this(o1, o2, null);
101   }
102
103   /**
104    * Creates a path and adds three objects to it.
105    *
106    * @param o1 the Object 1 to be added
107    * @param o2 the Object 2 to be added
108    * @param o3 the Object 3 to be added
109    */

110   public Path(Object JavaDoc o1, Object JavaDoc o2, Object JavaDoc o3) {
111     List list = new ArrayList();
112     addObjectToList(list, o1);
113     addObjectToList(list, o2);
114     addObjectToList(list, o3);
115
116     for (int i = 0; i < list.size(); i++) {
117       String JavaDoc s = (String JavaDoc) list.get(i);
118
119       if (s.equals("") || s.equals(".")) {
120         list.remove(i--);
121       } else if (s.equals("..")) {
122         if (i > 0 && !"..".equals(list.get(i - 1))) {
123           list.remove(i--);
124           list.remove(i--);
125         }
126       }
127     }
128
129     elements = (String JavaDoc[]) list.toArray(new String JavaDoc[list.size()]);
130     pathName = Utils.generateList(elements, "/");
131   }
132
133   protected void addObjectToList(List list, Object JavaDoc o) {
134     if (o == null) {
135       //
136
} else if (o instanceof Path) {
137       Path p = (Path) o;
138
139       for (int i = 0; i < p.getElementCount(); i++) {
140         list.add(p.getElementAt(i));
141       }
142     } else if (o instanceof String JavaDoc[]) {
143       String JavaDoc[] s = (String JavaDoc[]) o;
144
145       for (int i = 0; i < s.length; i++) {
146         addObjectToList(list, s[i]);
147       }
148     } else if (o instanceof Collection) {
149       Iterator i = ((Collection) o).iterator();
150
151       while (i.hasNext()) {
152         addObjectToList(list, i.next());
153       }
154     } else { // also works for java.io.File objects
155
StringTokenizer st = new StringTokenizer(o.toString(), "\\/");
156
157       while (st.hasMoreTokens()) {
158         list.add(st.nextToken());
159       }
160     }
161   }
162
163   /**
164    * Adds an object to the current path.
165    *
166    * @param o the Object to be added to the current path
167    *
168    * @return a new <code>Path</code> which is the combination of the current
169    * path and the added object
170    */

171   public Path add(Object JavaDoc o) {
172     return new Path(this, o);
173   }
174
175   /**
176    * Adds two objects to the current path.
177    *
178    * @param o1 Object 1 to add
179    * @param o2 Object 2 to add
180    *
181    * @return a new <code>Path</code> which is the combination of the current
182    * path and the added objects
183    */

184   public Path add(Object JavaDoc o1, Object JavaDoc o2) {
185     return new Path(this, o1, o2);
186   }
187
188   /**
189    * Return the parent of the current path. The parent of the root path is
190    * '..' (a <code>Path</code> with one element whose value is "..").
191    *
192    * @return the parent of the current path.
193    */

194   public Path getParent() {
195     return new Path(this, "..");
196   }
197
198   /**
199    * Returns a parent of the current path, whose element count is equal to the
200    * passed value.
201    *
202    * @param count the count
203    *
204    * @return the parent of he current path
205    */

206   public Path getPartial(int count) {
207     if (count < 0) {
208       throw new IllegalArgumentException JavaDoc("Negative level not allowed");
209     }
210
211     if (count == 0) {
212       return ROOT;
213     }
214
215     if (count >= elements.length) {
216       return this;
217     }
218
219     String JavaDoc[] partial = new String JavaDoc[count];
220     System.arraycopy(elements, 0, partial, 0, count);
221     return new Path(partial);
222   }
223
224   /**
225    * Returns the common part between the two Paths (between <code>this</code> path
226    * and the <code>other</code> path).
227    *
228    * @param other the second path
229    * @return the common path
230    */

231   public Path getCommonPath(Path other) {
232     return commonPart(this, other);
233   }
234
235   /**
236    * Cheks if this path is relative (when the first element of this path is "..")
237    *
238    * @return <code>true</code> when the first element of the current path is
239    * "..".
240    */

241   public boolean isRelative() {
242     return elements.length > 0 && elements[0].equals("..");
243   }
244
245   /**
246    * Returns true if this path is a ROOT path (when the path is empty)
247    *
248    * @return <code>true</code> if the path is empty.
249    */

250   public boolean isRoot() {
251     return elements.length == 0;
252   }
253
254   /**
255    * Checks if <code>this</code> path is a child of the <code>parent</code> path.
256    *
257    * @param parent the parent path
258    *
259    * @return <code>true</code> if the path current path is contained in the
260    * given path directly. Example:
261    * <pre>
262    * Path myPath = new Path("home/user/myfile.txt");
263    * myPath.isChildOf(new Path("nohome")); // returns false
264    * myPath.isChildOf(new Path("home")); // returns false
265    * myPath.isChildOf(new Path("home/user")); // returns true
266    * </pre>
267    */

268   public boolean isChildOf(Path parent) {
269     if (parent == null) {
270       return false;
271     }
272
273     int level = parent.getElementCount();
274
275     if (elements.length != level + 1) {
276       return false;
277     }
278
279     for (int i = 0; i < level; i++) {
280       if (!elements[i].equals(parent.getElementAt(i))) {
281         return false;
282       }
283     }
284
285     return true;
286   }
287
288   /**
289    * Checkes if the current path is contained in another path.
290    *
291    * @param root the othr path where to check if the current path is contained.
292    *
293    * @return <code>true</code> if the current path is contained in the
294    * given path (at any depth). Example:
295    * <pre>
296    * Path myPath = new Path("home/user/myfile.txt");
297    * myPath.isContainedIn(new Path("nohome")); // returns false
298    * myPath.isContainedIn(new Path("home")); // returns true
299    * myPath.isContainedIn(new Path("home/user")); // returns true
300    * </pre>
301    */

302   public boolean isContainedIn(Path root) {
303     if (root == null) {
304       return false;
305     }
306
307     if (root.isRelative()) {
308       throw new IllegalArgumentException JavaDoc("Root path can't be relative");
309     }
310
311     int level = root.getElementCount();
312
313     if (level > elements.length) {
314       return false;
315     }
316
317     for (int i = 0; i < level; i++) {
318       if (!elements[i].equals(root.getElementAt(i))) {
319         return false;
320       }
321     }
322
323     return true;
324   }
325
326   /**
327    * Returns the current path as relative to the given root. Example:
328    * <pre>
329    * Path myPath = new Path("home/user/myfile.txt");
330    * myPath.getRelativeTo(new Path("home")); // returns "user/myfile.txt"
331    * </pre>
332    *
333    * @param root the root to relate this path to.
334    *
335    * @return the current path as relative to the given root.
336    */

337   public Path getRelativeTo(Object JavaDoc root) {
338     Path rootPath = (root instanceof Path) ? (Path) root : new Path(root);
339
340     if (rootPath.isRelative()) {
341       throw new IllegalArgumentException JavaDoc("Root path can't be negative");
342     }
343
344     int i0 = 0;
345     int i1 = 0;
346
347     List list = new ArrayList();
348
349     while (i0 < rootPath.getElementCount() && i1 < elements.length &&
350            rootPath.getElementAt(i0).equals(elements[i1])) {
351       i0++;
352       i1++;
353     }
354
355     while (i0++ < rootPath.getElementCount()) {
356       list.add("..");
357     }
358
359     while (i1 < elements.length - 1) {
360       list.add(elements[i1++]);
361     }
362
363     if (i1 == elements.length - 1) {
364       list.add(elements[i1]);
365     }
366
367     return new Path(list);
368   }
369
370   /**
371    * Returns a <code>File</code> object relative to the given file.
372    *
373    * @param parent the parent file as a relative base
374    *
375    * @return the new relative file.
376    */

377   public File getFile(File parent) {
378     return elements.length == 0 ? parent : new File(parent, pathName);
379   }
380
381   /**
382    * Returns the number of elements of the current path. Example:
383    * <pre>
384    * new Path().getElementCount(); // returns 0
385    * new Path("home/user").getElementCount(); // returns 2
386    * new Path("../user").getElementCount(); // returns 2
387    *
388    * @return the number of elements the current path has.
389    */

390   public int getElementCount() {
391     return elements.length;
392   }
393
394   /**
395    * Returns the element at the given index. There is no check for the index
396    * value, so an <code>ArrayIndexOutOfBoundsException</code> might be thrown.
397    *
398    * @param index the index for the searched element.
399    *
400    * @return element at the given <code>index</code>
401    */

402   public String JavaDoc getElementAt(int index) {
403     return elements[index];
404   }
405
406   /**
407    * Returns the last element of the current path (usually the file name). For
408    * the root path the empty <code>String</code> is returned.
409    *
410    * @return the last element of the Path
411    */

412   public String JavaDoc getLastElement() {
413     return elements.length == 0 ? "" : elements[elements.length - 1];
414   }
415
416   /**
417    * Returns the <code>String</code> representation of the current path. The
418    * separator between elements is always a slash, regardless of the platform.
419    */

420   public String JavaDoc toString() {
421     return pathName;
422   }
423
424   /**
425    * Returns this path object encoded As a link: if the path is not empty, adds a
426    * slash at the beginning.
427    *
428    * @return a link represenation of this path.
429    */

430   public String JavaDoc getAsLink() {
431     return elements.length == 0 ? "" : '/' + pathName;
432   }
433
434   /**
435    * Compares this path to a new <code>Path</code> built by calling
436    * <code>new Path(o)</code>
437    */

438   public int compareTo(Object JavaDoc o) {
439     return compareTo(new Path(o));
440   }
441
442   /**
443    * Compares two paths. Please note that <code>path1.compareTo(path2)</code>
444    * is different from
445    * <code>path1.toString().compareTo(path2.toString())</code>, since this
446    * method compares the single elements of the paths.
447    *
448    * @param other the path to compare to this path
449    *
450    * @return -1, 0 or 1 as a compare result.
451    */

452   public int compareTo(Path other) {
453     int level = other.getElementCount();
454     int result;
455
456     for (int i = 0; i < elements.length; i++) {
457       if (i >= level) {
458         return 1;
459       }
460
461       result = elements[i].compareTo(other.getElementAt(i));
462
463       if (result != 0) {
464         return result;
465       }
466     }
467
468     return level > elements.length ? -1 : 0;
469   }
470
471   /**
472    * Returns the hash code of the <code>String</code> that representes this path.
473    */

474   public int hashCode() {
475     return pathName.hashCode();
476   }
477
478   /**
479    * Checks the two paths for equality. They are equal when their string
480    * representations are equal.
481    */

482   public boolean equals(Object JavaDoc o) {
483     if (o == null || !(o instanceof Path)) {
484       return false;
485     }
486
487     return pathName.equals(o.toString());
488   }
489
490   /**
491    * Returns the common part between the two Paths.
492    *
493    * @param p1 the Path 1
494    * @param p2 the Path 2
495    *
496    * @return a Path representing the common part between <code>p1</code> and <code>p2</code>
497    */

498   public static Path commonPart(Path p1, Path p2) {
499     int n = Math.min(p1.getElementCount(), p2.getElementCount());
500
501     for (int i = 0; i < n; i++) {
502       if (!p1.getElementAt(i).equals(p2.getElementAt(i))) {
503         return p1.getPartial(i);
504       }
505     }
506
507     return p1;
508   }
509
510   /**
511    * Returns the successor of this <code>Path</code>, as defined in the Javadoc
512    * <code>of java.util.TreeMap.subMap(...)</code>. This is useful when you need
513    * to use that method to get a <em>closed range</em> submap (or headmap, or
514    * tailmap) of <code>Path</code>s.
515    *
516    * @return the successor path
517    */

518   public Path successor() {
519     return (elements.length == 0) ? new Path("\0") : getParent().add(getLastElement() + '\0');
520   }
521
522   protected Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
523     //return super.clone();
524
return new Path(this);
525   }
526
527   public Path replace(int index, String JavaDoc element) {
528     int n = elements.length;
529
530     if (index < 0 || index >= n) {
531       throw new IllegalArgumentException JavaDoc("index out of range");
532     }
533
534     if (Utils.isNullOrEmpty(element)) {
535       throw new IllegalArgumentException JavaDoc("element value is missing or empty");
536     }
537
538     String JavaDoc[] elms = new String JavaDoc[n];
539     System.arraycopy(elements, 0, elms, 0, n);
540     elms[index] = element;
541     return new Path(elms);
542   }
543   
544   /**
545    * Returns a copy of the elements array.
546    */

547   public String JavaDoc[] getElements() {
548     String JavaDoc[] result = new String JavaDoc[elements.length];
549     System.arraycopy(elements, 0, result, 0, elements.length);
550     return result;
551   }
552 }
553
Popular Tags