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  
31      private final String scheme;
32      private final String absPath;
33      private FileType type;
34  
35      // Cached stuff
36      private String uri;
37      private String baseName;
38      private String rootUri;
39      private String extension;
40      private String decodedAbsPath;
41  
42      private String key = null;
43  
44      public AbstractFileName(final String scheme, final String absPath, final FileType type)
45      {
46          this.rootUri = null;
47          this.scheme = scheme;
48          this.type = type;
49          if (absPath != null && absPath.length() > 0)
50          {
51              if (absPath.length() > 1 && absPath.endsWith("/"))
52              {
53                  this.absPath = absPath.substring(0, absPath.length() - 1);
54              }
55              else
56              {
57                  this.absPath = absPath;
58              }
59          }
60          else
61          {
62              this.absPath = ROOT_PATH;
63          }
64      }
65  
66      @Override
67      public boolean equals(final Object o)
68      {
69          if (this == o)
70          {
71              return true;
72          }
73          if (o == null || getClass() != o.getClass())
74          {
75              return false;
76          }
77  
78          final AbstractFileName that = (AbstractFileName) o;
79  
80          return getKey().equals(that.getKey());
81      }
82  
83      @Override
84      public int hashCode()
85      {
86          return getKey().hashCode();
87      }
88  
89      /**
90       * Implement Comparable.
91       *
92       * @param obj another abstract filename
93       * @return negative number if less than, 0 if equal, positive if greater than.
94       */
95      @Override
96      public int compareTo(final FileName obj)
97      {
98          final AbstractFileName name = (AbstractFileName) obj;
99          return getKey().compareTo(name.getKey());
100     }
101 
102     /**
103      * Returns the URI of the file.
104      * @return the FileName as a URI.
105      */
106     @Override
107     public String toString()
108     {
109         return getURI();
110     }
111 
112     /**
113      * Factory method for creating name instances.
114      * @param absPath The absolute path.
115      * @param type The FileType.
116      * @return The FileName.
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      * @param buffer A StringBuilder to use to construct the URI.
124      * @param addPassword true if the password should be added, false otherwise.
125      */
126     protected abstract void appendRootUri(StringBuilder buffer, boolean addPassword);
127 
128     /**
129      * Returns the base name of the file.
130      * @return The base name of the file.
131      */
132     @Override
133     public String getBaseName()
134     {
135         if (baseName == null)
136         {
137             final int idx = getPath().lastIndexOf(SEPARATOR_CHAR);
138             if (idx == -1)
139             {
140                 baseName = getPath();
141             }
142             else
143             {
144                 baseName = getPath().substring(idx + 1);
145             }
146         }
147 
148         return baseName;
149     }
150 
151     /**
152      * Returns the absolute path of the file, relative to the root of the
153      * file system that the file belongs to.
154      * @return The path String.
155      */
156     @Override
157     public String getPath()
158     {
159         if (VFS.isUriStyle())
160         {
161             return absPath + getUriTrailer();
162         }
163         return absPath;
164     }
165 
166     protected String getUriTrailer()
167     {
168         return getType().hasChildren() ? "/" : "";
169     }
170 
171     /**
172      * Returns the decoded path.
173      * @return The decoded path String.
174      * @throws FileSystemException If an error occurs.
175      */
176     @Override
177     public String getPathDecoded() throws FileSystemException
178     {
179         if (decodedAbsPath == null)
180         {
181             decodedAbsPath = UriParser.decode(getPath());
182         }
183 
184         return decodedAbsPath;
185     }
186 
187     /**
188      * Returns the name of the parent of the file.
189      * @return the FileName of the parent.
190      */
191     @Override
192     public FileName getParent()
193     {
194         final String parentPath;
195         final int idx = getPath().lastIndexOf(SEPARATOR_CHAR);
196         if (idx == -1 || idx == getPath().length() - 1)
197         {
198             // No parent
199             return null;
200         }
201         else if (idx == 0)
202         {
203             // Root is the parent
204             parentPath = SEPARATOR;
205         }
206         else
207         {
208             parentPath = getPath().substring(0, idx);
209         }
210         return createName(parentPath, FileType.FOLDER);
211     }
212 
213     /**
214      * find the root of the filesystem.
215      * @return The root FileName.
216      */
217     @Override
218     public FileName getRoot()
219     {
220         FileName root = this;
221         while (root.getParent() != null)
222         {
223             root = root.getParent();
224         }
225 
226         return root;
227     }
228 
229     /**
230      * Returns the URI scheme of this file.
231      * @return The protocol used to access the file.
232      */
233     @Override
234     public String getScheme()
235     {
236         return scheme;
237     }
238 
239     /**
240      * Returns the absolute URI of the file.
241      * @return The absolute URI of the file.
242      */
243     @Override
244     public String getURI()
245     {
246         if (uri == null)
247         {
248             uri = createURI();
249         }
250         return uri;
251     }
252 
253     protected String createURI()
254     {
255         return createURI(false, true);
256     }
257 
258     /**
259      * Create a path that does not use the FileType since that field is not immutable.
260      * @return The key.
261      */
262     private String getKey()
263     {
264         if (key == null)
265         {
266             key = getURI();
267         }
268         return key;
269     }
270 
271     /**
272      * Returns the URI without a password.
273      *
274      * @return Returns the URI without a password.
275      */
276     @Override
277     public String getFriendlyURI()
278     {
279         return createURI(false, false);
280     }
281 
282     private String createURI(final boolean useAbsolutePath, final boolean usePassword)
283     {
284         final StringBuilder buffer = new StringBuilder();
285         appendRootUri(buffer, usePassword);
286         buffer.append(useAbsolutePath ? absPath : getPath());
287         return buffer.toString();
288     }
289 
290 
291     /**
292      * Converts a file name to a relative name, relative to this file name.
293      * @param name The FileName.
294      * @return The relative path to the file.
295      * @throws FileSystemException if an error occurs.
296      */
297     @Override
298     public String getRelativeName(final FileName name) throws FileSystemException
299     {
300         final String path = name.getPath();
301 
302         // Calculate the common prefix
303         final int basePathLen = getPath().length();
304         final int pathLen = path.length();
305 
306         // Deal with root
307         if (basePathLen == 1 && pathLen == 1)
308         {
309             return ".";
310         }
311         else if (basePathLen == 1)
312         {
313             return path.substring(1);
314         }
315 
316         final int maxlen = Math.min(basePathLen, pathLen);
317         int pos = 0;
318         for (; pos < maxlen && getPath().charAt(pos) == path.charAt(pos); pos++)
319         {
320         }
321 
322         if (pos == basePathLen && pos == pathLen)
323         {
324             // Same names
325             return ".";
326         }
327         else if (pos == basePathLen && pos < pathLen && path.charAt(pos) == SEPARATOR_CHAR)
328         {
329             // A descendent of the base path
330             return path.substring(pos + 1);
331         }
332 
333         // Strip the common prefix off the path
334         final StringBuilder buffer = new StringBuilder();
335         if (pathLen > 1 && (pos < pathLen || getPath().charAt(pos) != SEPARATOR_CHAR))
336         {
337             // Not a direct ancestor, need to back up
338             pos = getPath().lastIndexOf(SEPARATOR_CHAR, pos);
339             buffer.append(path.substring(pos));
340         }
341 
342         // Prepend a '../' for each element in the base path past the common
343         // prefix
344         buffer.insert(0, "..");
345         pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
346         while (pos != -1)
347         {
348             buffer.insert(0, "../");
349             pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
350         }
351 
352         return buffer.toString();
353     }
354 
355     /**
356      * Returns the root URI of the file system this file belongs to.
357      * @return The URI of the root.
358      */
359     @Override
360     public String getRootURI()
361     {
362         if (rootUri == null)
363         {
364             final StringBuilder buffer = new StringBuilder();
365             appendRootUri(buffer, true);
366             buffer.append(SEPARATOR_CHAR);
367             rootUri = buffer.toString().intern();
368         }
369         return rootUri;
370     }
371 
372     /**
373      * Returns the depth of this file name, within its file system.
374      * @return The depth of the file name.
375      */
376     @Override
377     public int getDepth()
378     {
379         final int len = getPath().length();
380         if (len == 0 || len == 1 && getPath().charAt(0) == SEPARATOR_CHAR)
381         {
382             return 0;
383         }
384         int depth = 1;
385         for (int pos = 0; pos > -1 && pos < len; depth++)
386         {
387             pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
388         }
389         return depth;
390     }
391 
392     /**
393      * Returns the extension of this file name.
394      * @return The file extension.
395      */
396     @Override
397     public String getExtension()
398     {
399         if (extension == null)
400         {
401             getBaseName();
402             final int pos = baseName.lastIndexOf('.');
403             // if ((pos == -1) || (pos == baseName.length() - 1))
404             // imario@ops.co.at: Review of patch from adagoubard@chello.nl
405             // do not treat filenames like
406             // .bashrc c:\windows\.java c:\windows\.javaws c:\windows\.jedit c:\windows\.appletviewer
407             // as extension
408             if (pos < 1 || pos == baseName.length() - 1)
409             {
410                 // No extension
411                 extension = "";
412             }
413             else
414             {
415                 extension = baseName.substring(pos + 1).intern();
416             }
417         }
418         return extension;
419     }
420 
421     /**
422      * Determines if another file name is an ancestor of this file name.
423      * @param ancestor The FileName to check.
424      * @return true if the FileName is an ancestor, false otherwise.
425      */
426     @Override
427     public boolean isAncestor(final FileName ancestor)
428     {
429         if (!ancestor.getRootURI().equals(getRootURI()))
430         {
431             return false;
432         }
433         return checkName(ancestor.getPath(), getPath(), NameScope.DESCENDENT);
434     }
435 
436     /**
437      * Determines if another file name is a descendent of this file name.
438      * @param descendent The FileName to check.
439      * @return true if the FileName is a descendent, false otherwise.
440      */
441     @Override
442     public boolean isDescendent(final FileName descendent)
443     {
444         return isDescendent(descendent, NameScope.DESCENDENT);
445     }
446 
447     /**
448      * Determines if another file name is a descendent of this file name.
449      * @param descendent The FileName to check.
450      * @param scope The NameScope.
451      * @return true if the FileName is a descendent, false otherwise.
452      */
453     @Override
454     public boolean isDescendent(final FileName descendent,
455                                 final NameScope scope)
456     {
457         if (!descendent.getRootURI().equals(getRootURI()))
458         {
459             return false;
460         }
461         return checkName(getPath(), descendent.getPath(), scope);
462     }
463 
464     /**
465      * Checks if this file name is a name for a regular file by using its type.
466      *
467      * @return true if this file is a regular file.
468      * @throws FileSystemException may be thrown by subclasses.
469      * @see #getType()
470      * @see FileType#FILE
471      */
472     @Override
473     public boolean isFile() throws FileSystemException
474     {
475         // Use equals instead of == to avoid any class loader worries.
476         return FileType.FILE.equals(this.getType());
477     }
478 
479     /**
480      * Returns the requested or current type of this name.
481      * <p>
482      * The "requested" type is the one determined during resolving the name.
483      *  n this case the name is a {@link FileType#FOLDER} if it ends with an "/" else
484      * it will be a {@link FileType#FILE}.
485      * <p>
486      * Once attached it will be changed to reflect the real type of this resource.
487      *
488      * @return {@link FileType#FOLDER} or {@link FileType#FILE}
489      */
490     @Override
491     public FileType getType()
492     {
493         return type;
494     }
495 
496     /**
497      * Sets the type of this file e.g. when it will be attached.
498      *
499      * @param type {@link FileType#FOLDER} or {@link FileType#FILE}
500      * @throws FileSystemException if an error occurs.
501      */
502     void setType(final FileType type) throws FileSystemException
503     {
504         if (type != FileType.FOLDER && type != FileType.FILE && type != FileType.FILE_OR_FOLDER)
505         {
506             throw new FileSystemException("vfs.provider/filename-type.error");
507         }
508 
509         this.type = type;
510     }
511 
512     /**
513      * Checks whether a path fits in a particular scope of another path.
514      *
515      * @param basePath An absolute, normalised path.
516      * @param path   An absolute, normalised path.
517      * @param scope The NameScope.
518      * @return true if the path fits in the scope, false otherwise.
519      */
520     public static boolean checkName(final String basePath,
521                                     final String path,
522                                     final NameScope scope)
523     {
524         if (scope == NameScope.FILE_SYSTEM)
525         {
526             // All good
527             return true;
528         }
529 
530         if (!path.startsWith(basePath))
531         {
532             return false;
533         }
534 
535         int baseLen = basePath.length();
536         if (VFS.isUriStyle())
537         {
538             // strip the trailing "/"
539             baseLen--;
540         }
541 
542         if (scope == NameScope.CHILD)
543         {
544             if (path.length() == baseLen
545                 || baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR
546                 || path.indexOf(SEPARATOR_CHAR, baseLen + 1) != -1)
547             {
548                 return false;
549             }
550         }
551         else if (scope == NameScope.DESCENDENT)
552         {
553             if (path.length() == baseLen
554                 || baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR)
555             {
556                 return false;
557             }
558         }
559         else if (scope == NameScope.DESCENDENT_OR_SELF)
560         {
561             if (baseLen > 1
562                 && path.length() > baseLen
563                 && path.charAt(baseLen) != SEPARATOR_CHAR)
564             {
565                 return false;
566             }
567         }
568         else if (scope != NameScope.FILE_SYSTEM)
569         {
570             throw new IllegalArgumentException();
571         }
572 
573         return true;
574     }
575 }