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 > 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 }
|