View Javadoc

1   /* This file is part of the project "Hilbert II" - http://www.qedeq.org" target="alexandria_uri">http://www.qedeq.org
2    *
3    * Copyright 2000-2014,  Michael Meyling <mime@qedeq.org>.
4    *
5    * "Hilbert II" is free software; you can redistribute
6    * it and/or modify it under the terms of the GNU General Public
7    * License as published by the Free Software Foundation; either
8    * version 2 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  
16  package org.qedeq.base.io;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  
21  import org.apache.commons.lang.ArrayUtils;
22  import org.qedeq.base.utility.EqualsUtility;
23  import org.qedeq.base.utility.StringUtility;
24  
25  
26  /**
27   * A file path that leads to a directory or file and is absolute or relative.
28   * This abstraction of a file location was done to create relative file paths.
29   * This class makes some assumptions about a file path:
30   * "/" is the directory separator, "/" is also the root directory and ".." specifies
31   * the parent directory. "." is the current directory.
32   * A directory path must end with a "/" if a path ends not with a "/" it will be a file path
33   * (but the empty path "" is a directory).
34   *
35   * @author  Michael Meyling
36   */
37  public final class Path {
38  
39      /** Directories that build up the path. If this is an absolute path the first
40       * name is empty. */
41      private final String[] path;
42  
43      /** File name. */
44      private final String name;
45  
46      /**
47       * Create file with given path and name.
48       *
49       * @param   filePath    Path to file with "/" as directory name separator. Relative directories
50       *                      are removed from the file path if possible.  Must not be
51       *                      <code>null</code>. Might be "" this is called the <em>empty path</em>.
52       */
53      public Path(final String filePath) {
54          final String[] p = StringUtility.split(filePath, "/");
55          name = p[p.length - 1];
56          final String[] p2 = new String[p.length - 1];
57          System.arraycopy(p, 0, p2, 0, p2.length);
58          path = removeRelativeDirs(p2);
59      }
60  
61      /**
62       * Create file with given path and name. Relative directories are removed from
63       * the file path if possible.
64       *
65       * @param   dirPath     Directory path to file with "/" as directory name separator.
66       *                      This value can end with a "/" but it must not be <code>null</code>.
67       * @param   fileName    File name. It should not contain "/" but this is not checked.
68       */
69      public Path(final String dirPath, final String fileName) {
70          this((dirPath.endsWith("/") ? StringUtility.split(dirPath.substring(0,
71              dirPath.length() - 1), "/") : StringUtility.split(dirPath, "/")), fileName);
72      }
73  
74      /**
75       * Create file with given path and name. Relative directories are removed
76       * from the file path if possible.
77       *
78       * @param   dirNames    Directory names. If this is an absolute path the first
79       *                      name is empty. Must not be <code>null</code>.
80       * @param   fileName    File name. It should not contain "/" but this is not checked.
81       */
82      public Path(final String[] dirNames, final String fileName) {
83          path = removeRelativeDirs(dirNames);
84          name = (fileName != null ? fileName : "");
85      }
86  
87      /**
88       * Create new file path relative to given one. If the original path or <code>filePath</code>
89       * is a relative path it will return <code>filePath</code>.
90       *
91       * @param   filePath    Path to file relative to <code>this</code>.
92       * @return  Relative file path (if possible).
93       */
94      public Path createRelative(final String filePath) {
95          final Path to = new Path(filePath);
96          if (isRelative()) {
97              // if from is relative, then we don't need to think further
98              return to;
99          }
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 > 0 && (path[0].length() == 0 || 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 > 0 && "..".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 = (Path) obj;
224         return EqualsUtility.equals(path, other.path) && name.equals(other.name);
225     }
226 
227     public int hashCode() {
228         return toString().hashCode();
229     }
230 
231 }
232