Path.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2013,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 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 
016 package org.qedeq.base.io;
017 
018 import java.util.ArrayList;
019 import java.util.List;
020 
021 import org.apache.commons.lang.ArrayUtils;
022 import org.qedeq.base.utility.EqualsUtility;
023 import org.qedeq.base.utility.StringUtility;
024 
025 
026 /**
027  * A file path that leads to a directory or file and is absolute or relative.
028  * This abstraction of a file location was done to create relative file paths.
029  * This class makes some assumptions about a file path:
030  * "/" is the directory separator, "/" is also the root directory and ".." specifies
031  * the parent directory. "." is the current directory.
032  * A directory path must end with a "/" if a path ends not with a "/" it will be a file path
033  * (but the empty path "" is a directory).
034  *
035  @author  Michael Meyling
036  */
037 public final class Path {
038 
039     /** Directories that build up the path. If this is an absolute path the first
040      * name is empty. */
041     private final String[] path;
042 
043     /** File name. */
044     private final String name;
045 
046     /**
047      * Create file with given path and name.
048      *
049      @param   filePath    Path to file with "/" as directory name separator. Relative directories
050      *                      are removed from the file path if possible.  Must not be
051      *                      <code>null</code>. Might be "" this is called the <em>empty path</em>.
052      */
053     public Path(final String filePath) {
054         final String[] p = StringUtility.split(filePath, "/");
055         name = p[p.length - 1];
056         final String[] p2 = new String[p.length - 1];
057         System.arraycopy(p, 0, p2, 0, p2.length);
058         path = removeRelativeDirs(p2);
059     }
060 
061     /**
062      * Create file with given path and name. Relative directories are removed from
063      * the file path if possible.
064      *
065      @param   dirPath     Directory path to file with "/" as directory name separator.
066      *                      This value can end with a "/" but it must not be <code>null</code>.
067      @param   fileName    File name. It should not contain "/" but this is not checked.
068      */
069     public Path(final String dirPath, final String fileName) {
070         this((dirPath.endsWith("/"? StringUtility.split(dirPath.substring(0,
071             dirPath.length() 1)"/": StringUtility.split(dirPath, "/")), fileName);
072     }
073 
074     /**
075      * Create file with given path and name. Relative directories are removed
076      * from the file path if possible.
077      *
078      @param   dirNames    Directory names. If this is an absolute path the first
079      *                      name is empty. Must not be <code>null</code>.
080      @param   fileName    File name. It should not contain "/" but this is not checked.
081      */
082     public Path(final String[] dirNames, final String fileName) {
083         path = removeRelativeDirs(dirNames);
084         name = (fileName != null ? fileName : "");
085     }
086 
087     /**
088      * Create new file path relative to given one. If the original path or <code>filePath</code>
089      * is a relative path it will return <code>filePath</code>.
090      *
091      @param   filePath    Path to file relative to <code>this</code>.
092      @return  Relative file path (if possible).
093      */
094     public Path createRelative(final String filePath) {
095         final Path to = new Path(filePath);
096         if (isRelative()) {
097             // if from is relative, then we don't need to think further
098             return to;
099         }
100         if (to.isRelative()) {
101             return to;
102         }
103         // both are absolute so we try to navigate within the file system
104         // to get a relative path
105         int max = 0;
106         while (max < path.length && max < to.path.length) {
107             if (!"..".equals(path[max]) && EqualsUtility.equals(path[max], to.path[max])) {
108                 max++;
109             else {
110                 break;
111             }
112         }
113         final String[] r = new String[path.length - max + to.path.length - max];
114         for (int i = max; i < path.length; i++) {
115             r[i - max"..";
116         }
117         for (int i = max; i < to.path.length; i++) {
118             r[i - max + path.length - max= to.path[i];
119         }
120         return new Path(r, to.name);
121     }
122 
123     /**
124      * Describes this path a directory?
125      *
126      @return  Is directory?
127      */
128     public boolean isDirectory() {
129         return name.length() == 0;
130     }
131 
132     /**
133      * Describes this path a file (and not a directory)?
134      *
135      @return  Is this a path to a file?
136      */
137     public boolean isFile() {
138         return !isDirectory();
139     }
140 
141     /**
142      * Is this an absolute path? If first path directory name is empty or ends with ":"
143      * (MS windows tribute) this is the case.
144      *
145      @return  Is absolute path?
146      */
147     public boolean isAbsolute() {
148         return path.length > && (path[0].length() == || path[0].endsWith(":"));
149     }
150 
151     /**
152      * Is this a relative path?
153      *
154      @return  Is this a relative path?
155      */
156     public boolean isRelative() {
157         return !isAbsolute();
158     }
159 
160     /**
161      * Get filename. Is "" if this is a pure directory path.
162      *
163      @return  File name. Might be "" but not <code>null</code>.
164      */
165     public String getFileName() {
166         return name;
167     }
168 
169     /**
170      * Get directory of this path. This might be a relative or absolute
171      * path and ends with "/". (Only the empty path "" has no ending "/".)
172      *
173      @return  Directory this path points to. Will end with "/" if this is not
174      *          an empty path.
175      */
176     public String getDirectory() {
177         StringBuffer result = new StringBuffer(256);
178         for (int i = 0; i < path.length; i++) {
179             result.append(path[i]).append("/");
180         }
181         return result.toString();
182     }
183 
184     /**
185      * Remove ".." and "." directories out of path if possible.
186      *
187      @param   dirNames    Directories that build up the path.
188      @return  Directories that build up the same path.
189      */
190     private String[] removeRelativeDirs(final String[] dirNames) {
191         List d = new ArrayList();
192         for (int i = 0; i < dirNames.length; i++) {
193             d.add(dirNames[i]);
194         }
195         for (int i = 0; i < d.size()) {
196             if (i > && "..".equals(d.get(i)) && !"".equals(d.get(i - 1))
197                     && !"..".equals(d.get(i - 1))) {
198                 d.remove(i - 1);
199                 d.remove(i - 1);
200                 i--;
201             else if (".".equals(d.get(i))) {
202                 d.remove(i);
203             else {
204                 i++;
205             }
206         }
207         return (String[]) d.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
208     }
209 
210     public String toString() {
211         StringBuffer result = new StringBuffer(256);
212         for (int i = 0; i < path.length; i++) {
213             result.append(path[i]).append("/");
214         }
215         result.append(name);
216         return result.toString();
217     }
218 
219     public boolean equals(final Object obj) {
220         if (!(obj instanceof Path)) {
221             return false;
222         }
223         final Path other = (Pathobj;
224         return EqualsUtility.equals(path, other.path&& name.equals(other.name);
225     }
226 
227     public int hashCode() {
228         return toString().hashCode();
229     }
230 
231 }