View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider;
18  
19  import org.apache.commons.vfs2.FileName;
20  import org.apache.commons.vfs2.FileSystemException;
21  import org.apache.commons.vfs2.FileType;
22  import org.apache.commons.vfs2.NameScope;
23  import org.apache.commons.vfs2.VFS;
24  
25  /**
26   * A default file name implementation.
27   */
28  public abstract class AbstractFileName implements FileName {
29  
30      // URI Characters that are possible in local file names, but must be escaped
31      // for proper URI handling.
32      //
33      // How reserved URI chars were selected:
34      //
35      // URIs can contain :, /, ?, #, @
36      // See http://download.oracle.com/javase/6/docs/api/java/net/URI.html
37      // http://tools.ietf.org/html/rfc3986#section-2.2
38      //
39      // Since : and / occur before the path, only chars after path are escaped (i.e., # and ?)
40      // ? is a reserved filesystem character for Windows and Unix, so can't be part of a file name.
41      // Therefore only # is a reserved char in a URI as part of the path that can be in the file name.
42      private static final char[] RESERVED_URI_CHARS = { '#' };
43  
44      private final String scheme;
45      private final String absPath;
46      private FileType type;
47  
48      // Cached stuff
49      private String uri;
50      private String baseName;
51      private String rootUri;
52      private String extension;
53      private String decodedAbsPath;
54  
55      private String key = null;
56  
57      public AbstractFileName(final String scheme, final String absPath, final FileType type) {
58          this.rootUri = null;
59          this.scheme = scheme;
60          this.type = type;
61          if (absPath != null && absPath.length() > 0) {
62              if (absPath.length() > 1 && absPath.endsWith("/")) {
63                  this.absPath = absPath.substring(0, absPath.length() - 1);
64              } else {
65                  this.absPath = absPath;
66              }
67          } else {
68              this.absPath = ROOT_PATH;
69          }
70      }
71  
72      @Override
73      public boolean equals(final Object o) {
74          if (this == o) {
75              return true;
76          }
77          if (o == null || getClass() != o.getClass()) {
78              return false;
79          }
80  
81          final AbstractFileName/../../org/apache/commons/vfs2/provider/AbstractFileName.html#AbstractFileName">AbstractFileName that = (AbstractFileName) o;
82  
83          return getKey().equals(that.getKey());
84      }
85  
86      @Override
87      public int hashCode() {
88          return getKey().hashCode();
89      }
90  
91      /**
92       * Implement Comparable.
93       *
94       * @param obj another abstract file name
95       * @return negative number if less than, 0 if equal, positive if greater than.
96       */
97      @Override
98      public int compareTo(final FileName obj) {
99          final AbstractFileName/../../org/apache/commons/vfs2/provider/AbstractFileName.html#AbstractFileName">AbstractFileName name = (AbstractFileName) obj;
100         return getKey().compareTo(name.getKey());
101     }
102 
103     /**
104      * Returns the URI of the file.
105      *
106      * @return the FileName as a URI.
107      */
108     @Override
109     public String toString() {
110         return getURI();
111     }
112 
113     /**
114      * Factory method for creating name instances.
115      *
116      * @param absolutePath The absolute path.
117      * @param fileType The FileType.
118      * @return The FileName.
119      */
120     public abstract FileName createName(String absolutePath, FileType fileType);
121 
122     /**
123      * Builds the root URI for this file name. Note that the root URI must not end with a separator character.
124      *
125      * @param buffer A StringBuilder to use to construct the URI.
126      * @param addPassword true if the password should be added, false otherwise.
127      */
128     protected abstract void appendRootUri(StringBuilder buffer, boolean addPassword);
129 
130     /**
131      * Returns the base name of the file.
132      *
133      * @return The base name of the file.
134      */
135     @Override
136     public String getBaseName() {
137         if (baseName == null) {
138             final int idx = getPath().lastIndexOf(SEPARATOR_CHAR);
139             if (idx == -1) {
140                 baseName = getPath();
141             } else {
142                 baseName = getPath().substring(idx + 1);
143             }
144         }
145 
146         return baseName;
147     }
148 
149     /**
150      * Returns the absolute path of the file, relative to the root of the file system that the file belongs to.
151      *
152      * @return The path String.
153      */
154     @Override
155     public String getPath() {
156         if (VFS.isUriStyle()) {
157             return absPath + getUriTrailer();
158         }
159         return absPath;
160     }
161 
162     protected String getUriTrailer() {
163         return getType().hasChildren() ? "/" : "";
164     }
165 
166     /**
167      * Returns the decoded path.
168      *
169      * @return The decoded path String.
170      * @throws FileSystemException If an error occurs.
171      */
172     @Override
173     public String getPathDecoded() throws FileSystemException {
174         if (decodedAbsPath == null) {
175             decodedAbsPath = UriParser.decode(getPath());
176         }
177 
178         return decodedAbsPath;
179     }
180 
181     /**
182      * Returns the name of the parent of the file.
183      *
184      * @return the FileName of the parent.
185      */
186     @Override
187     public FileName getParent() {
188         final String parentPath;
189         final int idx = getPath().lastIndexOf(SEPARATOR_CHAR);
190         if (idx == -1 || idx == getPath().length() - 1) {
191             // No parent
192             return null;
193         } else if (idx == 0) {
194             // Root is the parent
195             parentPath = SEPARATOR;
196         } else {
197             parentPath = getPath().substring(0, idx);
198         }
199         return createName(parentPath, FileType.FOLDER);
200     }
201 
202     /**
203      * find the root of the file system.
204      *
205      * @return The root FileName.
206      */
207     @Override
208     public FileName getRoot() {
209         FileName root = this;
210         while (root.getParent() != null) {
211             root = root.getParent();
212         }
213 
214         return root;
215     }
216 
217     /**
218      * Returns the URI scheme of this file.
219      *
220      * @return The protocol used to access the file.
221      */
222     @Override
223     public String getScheme() {
224         return scheme;
225     }
226 
227     /**
228      * Returns the absolute URI of the file.
229      *
230      * @return The absolute URI of the file.
231      */
232     @Override
233     public String getURI() {
234         if (uri == null) {
235             uri = createURI();
236         }
237         return uri;
238     }
239 
240     protected String createURI() {
241         return createURI(false, true);
242     }
243 
244     /**
245      * Create a path that does not use the FileType since that field is not immutable.
246      *
247      * @return The key.
248      */
249     private String getKey() {
250         if (key == null) {
251             key = getURI();
252         }
253         return key;
254     }
255 
256     /**
257      * Returns the URI without a password.
258      *
259      * @return Returns the URI without a password.
260      */
261     @Override
262     public String getFriendlyURI() {
263         return createURI(false, false);
264     }
265 
266     private String createURI(final boolean useAbsolutePath, final boolean usePassword) {
267         final StringBuilder buffer = new StringBuilder();
268         appendRootUri(buffer, usePassword);
269         buffer.append(handleURISpecialCharacters(useAbsolutePath ? absPath : getPath()));
270         return buffer.toString();
271     }
272 
273     private String handleURISpecialCharacters(String uri) {
274         if (uri != null && uri.length() > 0) {
275             try {
276                 // VFS-325: Handle URI special characters in file name
277                 // Decode the base uri and re-encode with URI special characters
278                 uri = UriParser.decode(uri);
279 
280                 return UriParser.encode(uri, RESERVED_URI_CHARS);
281             } catch (final FileSystemException e) {
282                 // Default to base uri value
283             }
284         }
285 
286         return uri;
287     }
288 
289     /**
290      * Converts a file name to a relative name, relative to this file name.
291      *
292      * @param name The FileName.
293      * @return The relative path to the file.
294      * @throws FileSystemException if an error occurs.
295      */
296     @Override
297     public String getRelativeName(final FileName name) throws FileSystemException {
298         final String path = name.getPath();
299 
300         // Calculate the common prefix
301         final int basePathLen = getPath().length();
302         final int pathLen = path.length();
303 
304         // Deal with root
305         if (basePathLen == 1 && pathLen == 1) {
306             return ".";
307         } else if (basePathLen == 1) {
308             return path.substring(1);
309         }
310 
311         final int maxlen = Math.min(basePathLen, pathLen);
312         int pos = 0;
313         for (; pos < maxlen && getPath().charAt(pos) == path.charAt(pos); pos++) {
314         }
315 
316         if (pos == basePathLen && pos == pathLen) {
317             // Same names
318             return ".";
319         } else if (pos == basePathLen && pos < pathLen && path.charAt(pos) == SEPARATOR_CHAR) {
320             // A descendent of the base path
321             return path.substring(pos + 1);
322         }
323 
324         // Strip the common prefix off the path
325         final StringBuilder buffer = new StringBuilder();
326         if (pathLen > 1 && (pos < pathLen || getPath().charAt(pos) != SEPARATOR_CHAR)) {
327             // Not a direct ancestor, need to back up
328             pos = getPath().lastIndexOf(SEPARATOR_CHAR, pos);
329             buffer.append(path.substring(pos));
330         }
331 
332         // Prepend a '../' for each element in the base path past the common
333         // prefix
334         buffer.insert(0, "..");
335         pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
336         while (pos != -1) {
337             buffer.insert(0, "../");
338             pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
339         }
340 
341         return buffer.toString();
342     }
343 
344     /**
345      * Returns the root URI of the file system this file belongs to.
346      *
347      * @return The URI of the root.
348      */
349     @Override
350     public String getRootURI() {
351         if (rootUri == null) {
352             final StringBuilder buffer = new StringBuilder();
353             appendRootUri(buffer, true);
354             buffer.append(SEPARATOR_CHAR);
355             rootUri = buffer.toString().intern();
356         }
357         return rootUri;
358     }
359 
360     /**
361      * Returns the depth of this file name, within its file system.
362      *
363      * @return The depth of the file name.
364      */
365     @Override
366     public int getDepth() {
367         final int len = getPath().length();
368         if (len == 0 || len == 1 && getPath().charAt(0) == SEPARATOR_CHAR) {
369             return 0;
370         }
371         int depth = 1;
372         for (int pos = 0; pos > -1 && pos < len; depth++) {
373             pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
374         }
375         return depth;
376     }
377 
378     /**
379      * Returns the extension of this file name.
380      *
381      * @return The file extension.
382      */
383     @Override
384     public String getExtension() {
385         if (extension == null) {
386             getBaseName();
387             final int pos = baseName.lastIndexOf('.');
388             // if ((pos == -1) || (pos == baseName.length() - 1))
389             // imario@ops.co.at: Review of patch from adagoubard@chello.nl
390             // do not treat file names like
391             // .bashrc c:\windows\.java c:\windows\.javaws c:\windows\.jedit c:\windows\.appletviewer
392             // as extension
393             if (pos < 1 || pos == baseName.length() - 1) {
394                 // No extension
395                 extension = "";
396             } else {
397                 extension = baseName.substring(pos + 1).intern();
398             }
399         }
400         return extension;
401     }
402 
403     /**
404      * Determines if another file name is an ancestor of this file name.
405      *
406      * @param ancestor The FileName to check.
407      * @return true if the FileName is an ancestor, false otherwise.
408      */
409     @Override
410     public boolean isAncestor(final FileName ancestor) {
411         if (!ancestor.getRootURI().equals(getRootURI())) {
412             return false;
413         }
414         return checkName(ancestor.getPath(), getPath(), NameScope.DESCENDENT);
415     }
416 
417     /**
418      * Determines if another file name is a descendent of this file name.
419      *
420      * @param descendent The FileName to check.
421      * @return true if the FileName is a descendent, false otherwise.
422      */
423     @Override
424     public boolean isDescendent(final FileName descendent) {
425         return isDescendent(descendent, NameScope.DESCENDENT);
426     }
427 
428     /**
429      * Determines if another file name is a descendent of this file name.
430      *
431      * @param descendent The FileName to check.
432      * @param scope The NameScope.
433      * @return true if the FileName is a descendent, false otherwise.
434      */
435     @Override
436     public boolean isDescendent(final FileName descendent, final NameScope scope) {
437         if (!descendent.getRootURI().equals(getRootURI())) {
438             return false;
439         }
440         return checkName(getPath(), descendent.getPath(), scope);
441     }
442 
443     /**
444      * Checks if this file name is a name for a regular file by using its type.
445      *
446      * @return true if this file is a regular file.
447      * @throws FileSystemException may be thrown by subclasses.
448      * @see #getType()
449      * @see FileType#FILE
450      */
451     @Override
452     public boolean isFile() throws FileSystemException {
453         // Use equals instead of == to avoid any class loader worries.
454         return FileType.FILE.equals(this.getType());
455     }
456 
457     /**
458      * Returns the requested or current type of this name.
459      * <p>
460      * The "requested" type is the one determined during resolving the name. n this case the name is a
461      * {@link FileType#FOLDER} if it ends with an "/" else it will be a {@link FileType#FILE}.
462      * </p>
463      * <p>
464      * Once attached it will be changed to reflect the real type of this resource.
465      * </p>
466      *
467      * @return {@link FileType#FOLDER} or {@link FileType#FILE}
468      */
469     @Override
470     public FileType getType() {
471         return type;
472     }
473 
474     /**
475      * Sets the type of this file e.g. when it will be attached.
476      *
477      * @param type {@link FileType#FOLDER} or {@link FileType#FILE}
478      * @throws FileSystemException if an error occurs.
479      */
480     void setType(final FileType type) throws FileSystemException {
481         if (type != FileType.FOLDER && type != FileType.FILE && type != FileType.FILE_OR_FOLDER) {
482             throw new FileSystemException("vfs.provider/filename-type.error");
483         }
484 
485         this.type = type;
486     }
487 
488     /**
489      * Checks whether a path fits in a particular scope of another path.
490      *
491      * @param basePath An absolute, normalised path.
492      * @param path An absolute, normalised path.
493      * @param scope The NameScope.
494      * @return true if the path fits in the scope, false otherwise.
495      */
496     public static boolean checkName(final String basePath, final String path, final NameScope scope) {
497         if (scope == NameScope.FILE_SYSTEM) {
498             // All good
499             return true;
500         }
501 
502         if (!path.startsWith(basePath)) {
503             return false;
504         }
505 
506         int baseLen = basePath.length();
507         if (VFS.isUriStyle()) {
508             // strip the trailing "/"
509             baseLen--;
510         }
511 
512         if (scope == NameScope.CHILD) {
513             if (path.length() == baseLen || baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR
514                     || path.indexOf(SEPARATOR_CHAR, baseLen + 1) != -1) {
515                 return false;
516             }
517         } else if (scope == NameScope.DESCENDENT) {
518             if (path.length() == baseLen || baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR) {
519                 return false;
520             }
521         } else if (scope == NameScope.DESCENDENT_OR_SELF) {
522             if (baseLen > 1 && path.length() > baseLen && path.charAt(baseLen) != SEPARATOR_CHAR) {
523                 return false;
524             }
525         } else if (scope != NameScope.FILE_SYSTEM) {
526             throw new IllegalArgumentException();
527         }
528 
529         return true;
530     }
531 }