001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.vfs.provider;
018:
019: import org.apache.commons.vfs.FileName;
020: import org.apache.commons.vfs.FileSystemException;
021: import org.apache.commons.vfs.FileType;
022: import org.apache.commons.vfs.NameScope;
023: import org.apache.commons.vfs.VFS;
024:
025: /**
026: * A default file name implementation.
027: *
028: * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
029: * @version $Revision: 537936 $ $Date: 2007-05-14 11:24:35 -0700 (Mon, 14 May 2007) $
030: */
031: public abstract class AbstractFileName implements FileName {
032:
033: private final String scheme;
034: private final String absPath;
035: private FileType type;
036:
037: // Cached stuff
038: private String uri;
039: private String baseName;
040: private String rootUri;
041: private String extension;
042: private String decodedAbsPath;
043:
044: private boolean calculateHashCode = true;
045: private int calculatedHashCode;
046:
047: public AbstractFileName(final String scheme, final String absPath,
048: FileType type) {
049: this .rootUri = null;
050: this .scheme = scheme;
051: this .type = type;
052: if (absPath != null && absPath.length() > 0) {
053: if (absPath.length() > 1 && absPath.endsWith("/")) {
054: this .absPath = absPath.substring(0,
055: absPath.length() - 1);
056: } else {
057: this .absPath = absPath;
058: }
059: } else {
060: this .absPath = ROOT_PATH;
061: }
062: }
063:
064: /**
065: * Returns the hashcode for this name.
066: */
067: public int hashCode() {
068: if (calculateHashCode) {
069: calculatedHashCode = (getRootURI().hashCode() ^ getPath()
070: .hashCode());
071: calculateHashCode = false;
072: }
073: return calculatedHashCode;
074: }
075:
076: /**
077: * Determines if this object is equal to another.
078: */
079: public boolean equals(final Object obj) {
080: if (!(obj instanceof AbstractFileName)) {
081: return false;
082: }
083: final AbstractFileName name = (AbstractFileName) obj;
084: return (getRootURI().equals(name.getRootURI()) && getPath()
085: .equals(name.getPath()));
086: }
087:
088: /**
089: * Implement Comparable
090: *
091: * @param obj another abstractfilename
092: */
093: public int compareTo(Object obj) {
094: final AbstractFileName name = (AbstractFileName) obj;
095: int ret = getRootURI().compareTo(name.getRootURI());
096: if (ret != 0) {
097: return ret;
098: }
099:
100: // return absPath.compareTo(name.absPath);
101: try {
102: return getPathDecoded().compareTo(name.getPathDecoded());
103: } catch (FileSystemException e) {
104: throw new RuntimeException(e.getMessage());
105: }
106: }
107:
108: /**
109: * Returns the URI of the file.
110: */
111: public String toString() {
112: return getURI();
113: }
114:
115: /**
116: * Factory method for creating name instances.
117: */
118: public abstract FileName createName(String absPath, FileType type);
119:
120: /**
121: * Builds the root URI for this file name. Note that the root URI must not
122: * end with a separator character.
123: */
124: protected abstract void appendRootUri(StringBuffer buffer,
125: boolean addPassword);
126:
127: /**
128: * Returns the base name of the file.
129: */
130: public String getBaseName() {
131: if (baseName == null) {
132: final int idx = getPath().lastIndexOf(SEPARATOR_CHAR);
133: if (idx == -1) {
134: baseName = getPath();
135: } else {
136: baseName = getPath().substring(idx + 1);
137: }
138: }
139:
140: return baseName;
141: }
142:
143: /**
144: * Returns the absolute path of the file, relative to the root of the
145: * file system that the file belongs to.
146: */
147: public String getPath() {
148: if (VFS.isUriStyle()) {
149: return absPath + getUriTrailer();
150: }
151: return absPath;
152: }
153:
154: protected String getUriTrailer() {
155: return getType().hasChildren() ? "/" : "";
156: }
157:
158: public String getPathDecoded() throws FileSystemException {
159: if (decodedAbsPath == null) {
160: decodedAbsPath = UriParser.decode(getPath());
161: }
162:
163: return decodedAbsPath;
164: }
165:
166: /**
167: * Returns the name of the parent of the file.
168: */
169: public FileName getParent() {
170: final String parentPath;
171: final int idx = getPath().lastIndexOf(SEPARATOR_CHAR);
172: if (idx == -1 || idx == getPath().length() - 1) {
173: // No parent
174: return null;
175: } else if (idx == 0) {
176: // Root is the parent
177: parentPath = SEPARATOR;
178: } else {
179: parentPath = getPath().substring(0, idx);
180: }
181: return createName(parentPath, FileType.FOLDER);
182: }
183:
184: /**
185: * find the root of the filesystem
186: */
187: public FileName getRoot() {
188: FileName root = this ;
189: while (root.getParent() != null) {
190: root = root.getParent();
191: }
192:
193: return root;
194: }
195:
196: /**
197: * Returns the URI scheme of this file.
198: */
199: public String getScheme() {
200: return scheme;
201: }
202:
203: /**
204: * Returns the absolute URI of the file.
205: */
206: public String getURI() {
207: if (uri == null) {
208: uri = createURI();
209: }
210: return uri;
211: }
212:
213: protected String createURI() {
214: final StringBuffer buffer = new StringBuffer();
215: appendRootUri(buffer, true);
216: buffer.append(getPath());
217: return buffer.toString();
218: }
219:
220: /**
221: * Converts a file name to a relative name, relative to this file name.
222: */
223: public String getRelativeName(final FileName name)
224: throws FileSystemException {
225: final String path = name.getPath();
226:
227: // Calculate the common prefix
228: final int basePathLen = getPath().length();
229: final int pathLen = path.length();
230:
231: // Deal with root
232: if (basePathLen == 1 && pathLen == 1) {
233: return ".";
234: } else if (basePathLen == 1) {
235: return path.substring(1);
236: }
237:
238: final int maxlen = Math.min(basePathLen, pathLen);
239: int pos = 0;
240: for (; pos < maxlen
241: && getPath().charAt(pos) == path.charAt(pos); pos++) {
242: }
243:
244: if (pos == basePathLen && pos == pathLen) {
245: // Same names
246: return ".";
247: } else if (pos == basePathLen && pos < pathLen
248: && path.charAt(pos) == SEPARATOR_CHAR) {
249: // A descendent of the base path
250: return path.substring(pos + 1);
251: }
252:
253: // Strip the common prefix off the path
254: final StringBuffer buffer = new StringBuffer();
255: if (pathLen > 1
256: && (pos < pathLen || getPath().charAt(pos) != SEPARATOR_CHAR)) {
257: // Not a direct ancestor, need to back up
258: pos = getPath().lastIndexOf(SEPARATOR_CHAR, pos);
259: buffer.append(path.substring(pos));
260: }
261:
262: // Prepend a '../' for each element in the base path past the common
263: // prefix
264: buffer.insert(0, "..");
265: pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
266: while (pos != -1) {
267: buffer.insert(0, "../");
268: pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
269: }
270:
271: return buffer.toString();
272: }
273:
274: /**
275: * Returns the root URI of the file system this file belongs to.
276: */
277: public String getRootURI() {
278: if (rootUri == null) {
279: final StringBuffer buffer = new StringBuffer();
280: appendRootUri(buffer, true);
281: buffer.append(SEPARATOR_CHAR);
282: rootUri = buffer.toString().intern();
283: }
284: return rootUri;
285: }
286:
287: /**
288: * Returns the depth of this file name, within its file system.
289: */
290: public int getDepth() {
291: final int len = getPath().length();
292: if (len == 0
293: || (len == 1 && getPath().charAt(0) == SEPARATOR_CHAR)) {
294: return 0;
295: }
296: int depth = 1;
297: for (int pos = 0; pos > -1 && pos < len; depth++) {
298: pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
299: }
300: return depth;
301: }
302:
303: /**
304: * Returns the extension of this file name.
305: */
306: public String getExtension() {
307: if (extension == null) {
308: getBaseName();
309: final int pos = baseName.lastIndexOf('.');
310: // if ((pos == -1) || (pos == baseName.length() - 1))
311: // imario@ops.co.at: Review of patch from adagoubard@chello.nl
312: // do not treat filenames like
313: // .bashrc c:\windows\.java c:\windows\.javaws c:\windows\.jedit c:\windows\.appletviewer
314: // as extension
315: if ((pos < 1) || (pos == baseName.length() - 1)) {
316: // No extension
317: extension = "";
318: } else {
319: extension = baseName.substring(pos + 1).intern();
320: }
321: }
322: return extension;
323: }
324:
325: /**
326: * Determines if another file name is an ancestor of this file name.
327: */
328: public boolean isAncestor(final FileName ancestor) {
329: if (!ancestor.getRootURI().equals(getRootURI())) {
330: return false;
331: }
332: return checkName(ancestor.getPath(), getPath(),
333: NameScope.DESCENDENT);
334: }
335:
336: /**
337: * Determines if another file name is a descendent of this file name.
338: */
339: public boolean isDescendent(final FileName descendent) {
340: return isDescendent(descendent, NameScope.DESCENDENT);
341: }
342:
343: /**
344: * Determines if another file name is a descendent of this file name.
345: */
346: public boolean isDescendent(final FileName descendent,
347: final NameScope scope) {
348: if (!descendent.getRootURI().equals(getRootURI())) {
349: return false;
350: }
351: return checkName(getPath(), descendent.getPath(), scope);
352: }
353:
354: /**
355: * Returns the requested or current type of this name. <br />
356: * <p/>
357: * The "requested" type is the one determined during resolving the name. <br/>
358: * In this case the name is a {@link FileType#FOLDER} if it ends with an "/" else
359: * it will be a {@link FileType#FILE}<br/>
360: * </p>
361: * <p/>
362: * Once attached it will be changed to reflect the real type of this resource.
363: * </p>
364: *
365: * @return {@link FileType#FOLDER} or {@link FileType#FILE}
366: */
367: public FileType getType() {
368: return type;
369: }
370:
371: /**
372: * sets the type of this file e.g. when it will be attached.
373: *
374: * @param type {@link FileType#FOLDER} or {@link FileType#FILE}
375: */
376: void setType(FileType type) throws FileSystemException {
377: if (type != FileType.FOLDER && type != FileType.FILE
378: && type != FileType.FILE_OR_FOLDER) {
379: throw new FileSystemException(
380: "vfs.provider/filename-type.error");
381: }
382:
383: this .type = type;
384: }
385:
386: /**
387: * Checks whether a path fits in a particular scope of another path.
388: *
389: * @param basePath An absolute, normalised path.
390: * @param path An absolute, normalised path.
391: */
392: public static boolean checkName(final String basePath,
393: final String path, final NameScope scope) {
394: if (scope == NameScope.FILE_SYSTEM) {
395: // All good
396: return true;
397: }
398:
399: if (!path.startsWith(basePath)) {
400: return false;
401: }
402:
403: int baseLen = basePath.length();
404: if (VFS.isUriStyle()) {
405: // strip the trailing "/"
406: baseLen--;
407: }
408:
409: if (scope == NameScope.CHILD) {
410: if (path.length() == baseLen
411: || (baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR)
412: || path.indexOf(SEPARATOR_CHAR, baseLen + 1) != -1) {
413: return false;
414: }
415: } else if (scope == NameScope.DESCENDENT) {
416: if (path.length() == baseLen
417: || (baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR)) {
418: return false;
419: }
420: } else if (scope == NameScope.DESCENDENT_OR_SELF) {
421: if (baseLen > 1 && path.length() > baseLen
422: && path.charAt(baseLen) != SEPARATOR_CHAR) {
423: return false;
424: }
425: } else if (scope != NameScope.FILE_SYSTEM) {
426: throw new IllegalArgumentException();
427: }
428:
429: return true;
430: }
431:
432: /**
433: * returns a "friendly path", this is a path without a password.
434: */
435: public String getFriendlyURI() {
436: final StringBuffer buffer = new StringBuffer();
437: appendRootUri(buffer, false);
438: buffer.append(getPath());
439: return buffer.toString();
440: }
441: }
|