001: /*
002: * MeshCMS - A simple CMS based on SiteMesh
003: * Copyright (C) 2004-2007 Luciano Vernaschi
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License
007: * as published by the Free Software Foundation; either version 2
008: * of the License, or (at your option) any later version.
009: *
010: * This program is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU General Public License for more details.
014: *
015: * You should have received a copy of the GNU General Public License
016: * along with this program; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
018: *
019: * You can contact the author at http://www.cromoteca.com
020: * and at info@cromoteca.com
021: */
022:
023: package org.meshcms.util;
024:
025: import java.io.*;
026: import java.util.*;
027:
028: /**
029: * An abstract representation of a file path. The root of the path is
030: * undefined, and the path can be relative (i.e. can start with '..').
031: *
032: * Example of paths are:
033: *
034: * <ul>
035: * <li>(the empty path)<br>This is meant as the (relative) root;</li>
036: * <li>filename.txt</li>
037: * <li>home/user/document.html</li>
038: * <li>../../directoryname</li>
039: * </ul>
040: *
041: * A <code>Path</code> can be created from any object. When you call a
042: * constructor, the path is initialized as empty, then the objects passed to
043: * the constructor are added to it.
044: *
045: * When all objects have been added, the path is simplified by removing
046: * redundant elements. For example, "home/user/../otheruser"
047: * is reduced to "home/otheruser".
048: *
049: * After the constructor returns, the
050: * <code>Path</code> object is immutable. When you call a method to modify
051: * it (like one of the <code>add()</code> methods), it returns a new
052: * <code>Path</code> that is the result of the requested operation.
053: *
054: * The objects are added as follows:
055: *
056: * <ul>
057: * <li>if the object is null, nothing is added;</li>
058: * <li>if it is another <code>Path</code>, its elements are added;</li>
059: * <li>if it is a <code>String</code>, it is split in tokens (divided by
060: * slashes or backslashes) and these tokens are added as elements;</li>
061: * <li>if it is a <code>Collection</code>, any member of the
062: * <code>Collection</code> is added as a separate object;</li>
063: * <li>if it is an array of <code>String</code>s, any member of the array is
064: * added as a separate <code>String</code> (to be tokenized);</li>
065: * <li>if it is another kind of object, its <code>toString()</code> method is
066: * called and the returned <code>String</code> is tokenized and added.</li>
067: * </ul>
068: *
069: * @author Luciano Vernaschi
070: */
071: public class Path implements Comparable, Serializable, Cloneable {
072: protected String pathName;
073: protected String[] elements;
074:
075: public static final Path ROOT = new Path();
076:
077: /**
078: * Creates an empty path.
079: */
080: public Path() {
081: this (null, null, null);
082: }
083:
084: /**
085: * Creates a path and adds an object to it.
086: *
087: * @param o the Object to be added to this new path
088: */
089: public Path(Object o) {
090: this (o, null, null);
091: }
092:
093: /**
094: * Creates a path and adds two objects to it.
095: *
096: * @param o1 the Object 1 to be added
097: * @param o2 the Object 2 to be added
098: */
099: public Path(Object o1, Object 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 o1, Object o2, Object 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 s = (String) 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[]) list.toArray(new String[list.size()]);
130: pathName = Utils.generateList(elements, "/");
131: }
132:
133: protected void addObjectToList(List list, Object 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[]) {
143: String[] s = (String[]) 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:
158: while (st.hasMoreTokens()) {
159: list.add(st.nextToken());
160: }
161: }
162: }
163:
164: /**
165: * Adds an object to the current path.
166: *
167: * @param o the Object to be added to the current path
168: *
169: * @return a new <code>Path</code> which is the combination of the current
170: * path and the added object
171: */
172: public Path add(Object o) {
173: return new Path(this , o);
174: }
175:
176: /**
177: * Adds two objects to the current path.
178: *
179: * @param o1 Object 1 to add
180: * @param o2 Object 2 to add
181: *
182: * @return a new <code>Path</code> which is the combination of the current
183: * path and the added objects
184: */
185: public Path add(Object o1, Object o2) {
186: return new Path(this , o1, o2);
187: }
188:
189: /**
190: * Return the parent of the current path. The parent of the root path is
191: * '..' (a <code>Path</code> with one element whose value is "..").
192: *
193: * @return the parent of the current path.
194: */
195: public Path getParent() {
196: return new Path(this , "..");
197: }
198:
199: /**
200: * Returns a parent of the current path, whose element count is equal to the
201: * passed value.
202: *
203: * @param count the count
204: *
205: * @return the parent of he current path
206: */
207: public Path getPartial(int count) {
208: if (count < 0) {
209: throw new IllegalArgumentException(
210: "Negative level not allowed");
211: }
212:
213: if (count == 0) {
214: return ROOT;
215: }
216:
217: if (count >= elements.length) {
218: return this ;
219: }
220:
221: String[] partial = new String[count];
222: System.arraycopy(elements, 0, partial, 0, count);
223: return new Path(partial);
224: }
225:
226: /**
227: * Returns the common part between the two Paths (between <code>this</code> path
228: * and the <code>other</code> path).
229: *
230: * @param other the second path
231: * @return the common path
232: */
233: public Path getCommonPath(Path other) {
234: return commonPart(this , other);
235: }
236:
237: /**
238: * Cheks if this path is relative (when the first element of this path is "..")
239: *
240: * @return <code>true</code> when the first element of the current path is
241: * "..".
242: */
243: public boolean isRelative() {
244: return elements.length > 0 && elements[0].equals("..");
245: }
246:
247: /**
248: * Returns true if this path is a ROOT path (when the path is empty)
249: *
250: * @return <code>true</code> if the path is empty.
251: */
252: public boolean isRoot() {
253: return elements.length == 0;
254: }
255:
256: /**
257: * Checks if <code>this</code> path is a child of the <code>parent</code> path.
258: *
259: * @param parent the parent path
260: *
261: * @return <code>true</code> if the path current path is contained in the
262: * given path directly. Example:
263: * <pre>
264: * Path myPath = new Path("home/user/myfile.txt");
265: * myPath.isChildOf(new Path("nohome")); // returns false
266: * myPath.isChildOf(new Path("home")); // returns false
267: * myPath.isChildOf(new Path("home/user")); // returns true
268: * </pre>
269: */
270: public boolean isChildOf(Path parent) {
271: if (parent == null) {
272: return false;
273: }
274:
275: int level = parent.getElementCount();
276:
277: if (elements.length != level + 1) {
278: return false;
279: }
280:
281: for (int i = 0; i < level; i++) {
282: if (!elements[i].equals(parent.getElementAt(i))) {
283: return false;
284: }
285: }
286:
287: return true;
288: }
289:
290: /**
291: * Checkes if the current path is contained in another path.
292: *
293: * @param root the othr path where to check if the current path is contained.
294: *
295: * @return <code>true</code> if the current path is contained in the
296: * given path (at any depth). Example:
297: * <pre>
298: * Path myPath = new Path("home/user/myfile.txt");
299: * myPath.isContainedIn(new Path("nohome")); // returns false
300: * myPath.isContainedIn(new Path("home")); // returns true
301: * myPath.isContainedIn(new Path("home/user")); // returns true
302: * </pre>
303: */
304: public boolean isContainedIn(Path root) {
305: return root == null ? false : !getRelativeTo(root).isRelative();
306: }
307:
308: /**
309: * Returns the current path as relative to the given root. Example:
310: * <pre>
311: * Path myPath = new Path("home/user/myfile.txt");
312: * myPath.getRelativeTo(new Path("home")); // returns "user/myfile.txt"
313: * </pre>
314: *
315: * @param root the root to relate this path to.
316: *
317: * @return the current path as relative to the given root.
318: */
319: public Path getRelativeTo(Object root) {
320: Path rootPath = (root instanceof Path) ? (Path) root
321: : new Path(root);
322:
323: int i0 = 0;
324: int i1 = 0;
325:
326: List list = new ArrayList();
327:
328: while (i0 < rootPath.getElementCount() && i1 < elements.length
329: && rootPath.getElementAt(i0).equals(elements[i1])) {
330: i0++;
331: i1++;
332: }
333:
334: while (i0++ < rootPath.getElementCount()) {
335: list.add("..");
336: }
337:
338: while (i1 <= elements.length - 1) {
339: list.add(elements[i1++]);
340: }
341:
342: return new Path(list);
343: }
344:
345: /**
346: * Returns a <code>File</code> object relative to the given file.
347: *
348: * @param parent the parent file as a relative base
349: *
350: * @return the new relative file.
351: */
352: public File getFile(File parent) {
353: return elements.length == 0 ? parent : new File(parent,
354: pathName);
355: }
356:
357: /**
358: * Returns the number of elements of the current path. Example:
359: * <pre>
360: * new Path().getElementCount(); // returns 0
361: * new Path("home/user").getElementCount(); // returns 2
362: * new Path("../user").getElementCount(); // returns 2
363: *
364: * @return the number of elements the current path has.
365: */
366: public int getElementCount() {
367: return elements.length;
368: }
369:
370: /**
371: * Returns the element at the given index. There is no check for the index
372: * value, so an <code>ArrayIndexOutOfBoundsException</code> might be thrown.
373: *
374: * @param index the index for the searched element.
375: *
376: * @return element at the given <code>index</code>
377: */
378: public String getElementAt(int index) {
379: return elements[index];
380: }
381:
382: /**
383: * Returns the last element of the current path (usually the file name). For
384: * the root path the empty <code>String</code> is returned.
385: *
386: * @return the last element of the Path
387: */
388: public String getLastElement() {
389: return elements.length == 0 ? ""
390: : elements[elements.length - 1];
391: }
392:
393: /**
394: * Returns the <code>String</code> representation of the current path. The
395: * separator between elements is always a slash, regardless of the platform.
396: */
397: public String toString() {
398: return pathName;
399: }
400:
401: /**
402: * Returns this path object encoded As a link: if the path is not empty, adds a
403: * slash at the beginning.
404: *
405: * @return a link represenation of this path.
406: */
407: public String getAsLink() {
408: String s = pathName;
409:
410: if (elements.length == 0) {
411: s = "";
412: } else if (!isRelative()) {
413: s = '/' + s;
414: }
415:
416: return s;
417: }
418:
419: /**
420: * Compares this path to a new <code>Path</code> built by calling
421: * <code>new Path(o)</code>
422: */
423: public int compareTo(Object o) {
424: return compareTo(new Path(o));
425: }
426:
427: /**
428: * Compares two paths. Please note that <code>path1.compareTo(path2)</code>
429: * is different from
430: * <code>path1.toString().compareTo(path2.toString())</code>, since this
431: * method compares the single elements of the paths.
432: *
433: * @param other the path to compare to this path
434: *
435: * @return -1, 0 or 1 as a compare result.
436: */
437: public int compareTo(Path other) {
438: int level = other.getElementCount();
439: int result;
440:
441: for (int i = 0; i < elements.length; i++) {
442: if (i >= level) {
443: return 1;
444: }
445:
446: result = elements[i].compareTo(other.getElementAt(i));
447:
448: if (result != 0) {
449: return result;
450: }
451: }
452:
453: return level > elements.length ? -1 : 0;
454: }
455:
456: /**
457: * Returns the hash code of the <code>String</code> that representes this path.
458: */
459: public int hashCode() {
460: return pathName.hashCode();
461: }
462:
463: /**
464: * Checks the two paths for equality. They are equal when their string
465: * representations are equal.
466: */
467: public boolean equals(Object o) {
468: if (o == null || !(o instanceof Path)) {
469: return false;
470: }
471:
472: return pathName.equals(o.toString());
473: }
474:
475: /**
476: * Returns the common part between the two Paths.
477: *
478: * @param p1 the Path 1
479: * @param p2 the Path 2
480: *
481: * @return a Path representing the common part between <code>p1</code> and <code>p2</code>
482: */
483: public static Path commonPart(Path p1, Path p2) {
484: int n = Math.min(p1.getElementCount(), p2.getElementCount());
485:
486: for (int i = 0; i < n; i++) {
487: if (!p1.getElementAt(i).equals(p2.getElementAt(i))) {
488: return p1.getPartial(i);
489: }
490: }
491:
492: return p1;
493: }
494:
495: /**
496: * Returns the successor of this <code>Path</code>, as defined in the Javadoc
497: * <code>of java.util.TreeMap.subMap(...)</code>. This is useful when you need
498: * to use that method to get a <em>closed range</em> submap (or headmap, or
499: * tailmap) of <code>Path</code>s.
500: *
501: * @return the successor path
502: */
503: public Path successor() {
504: return (elements.length == 0) ? new Path("\0") : getParent()
505: .add(getLastElement() + '\0');
506: }
507:
508: protected Object clone() throws CloneNotSupportedException {
509: //return super.clone();
510: return new Path(this );
511: }
512:
513: public Path replace(int index, String element) {
514: int n = elements.length;
515:
516: if (index < 0 || index >= n) {
517: throw new IllegalArgumentException("index out of range");
518: }
519:
520: if (Utils.isNullOrEmpty(element)) {
521: throw new IllegalArgumentException(
522: "element value is missing or empty");
523: }
524:
525: String[] elms = new String[n];
526: System.arraycopy(elements, 0, elms, 0, n);
527: elms[index] = element;
528: return new Path(elms);
529: }
530:
531: /**
532: * Returns a copy of the elements array.
533: */
534: public String[] getElements() {
535: String[] result = new String[elements.length];
536: System.arraycopy(elements, 0, result, 0, elements.length);
537: return result;
538: }
539: }
|