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.vfs.provider;
18  
19  import org.apache.commons.vfs.FileName;
20  import org.apache.commons.vfs.FileSystemException;
21  import org.apache.commons.vfs.FileType;
22  import org.apache.commons.vfs.NameScope;
23  import org.apache.commons.vfs.VFS;
24  
25  /***
26   * A default file name implementation.
27   *
28   * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
29   * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
30   */
31  public abstract class AbstractFileName
32      implements FileName
33  {
34      
35      private final String scheme;
36      private final String absPath;
37      private FileType type;
38  
39      // Cached stuff
40      private String uri;
41      private String baseName;
42      private String rootUri;
43      private String extension;
44      private String decodedAbsPath;
45  
46      public AbstractFileName(final String scheme,
47                              final String absPath, FileType type)
48      {
49          this.rootUri = null;
50          this.scheme = scheme;
51          this.type = type;
52          if (absPath != null && absPath.length() > 0)
53          {
54              if (absPath.length() > 1 && absPath.endsWith("/"))
55              {
56                  this.absPath = absPath.substring(0, absPath.length() - 1);
57              }
58              else
59              {
60                  this.absPath = absPath;
61              }
62          }
63          else
64          {
65              this.absPath = ROOT_PATH;
66          }
67      }
68  
69      /***
70       * Returns the hashcode for this name.
71       */
72      public int hashCode()
73      {
74          return (getRootURI().hashCode() ^ getPath().hashCode());
75      }
76  
77      /***
78       * Determines if this object is equal to another.
79       */
80      public boolean equals(final Object obj)
81      {
82          final AbstractFileName name = (AbstractFileName) obj;
83          return (getRootURI().equals(name.getRootURI()) && getPath().equals(name.getPath()));
84      }
85  
86      /***
87       * Implement Comparable
88       *
89       * @param obj another abstractfilename
90       */
91      public int compareTo(Object obj)
92      {
93          final AbstractFileName name = (AbstractFileName) obj;
94          int ret = getRootURI().compareTo(name.getRootURI());
95          if (ret != 0)
96          {
97              return ret;
98          }
99  
100         // return absPath.compareTo(name.absPath);
101         try
102         {
103             return getPathDecoded().compareTo(name.getPathDecoded());
104         }
105         catch (FileSystemException e)
106         {
107             throw new RuntimeException(e.getMessage());
108         }
109     }
110 
111     /***
112      * Returns the URI of the file.
113      */
114     public String toString()
115     {
116         return getURI();
117     }
118 
119     /***
120      * Factory method for creating name instances.
121      */
122     public abstract FileName createName(String absPath, FileType type);
123 
124     /***
125      * Builds the root URI for this file name.  Note that the root URI must not
126      * end with a separator character.
127      */
128     protected abstract void appendRootUri(StringBuffer buffer, boolean addPassword);
129 
130     /***
131      * Returns the base name of the file.
132      */
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      */
155     public String getPath()
156     {
157         if (VFS.isUriStyle())
158         {
159             return absPath + getUriTrailer();
160         }
161         return absPath;
162     }
163 
164     protected String getUriTrailer()
165     {
166         return getType().hasChildren() ? "/" : "";
167     }
168 
169     public String getPathDecoded() throws FileSystemException
170     {
171         if (decodedAbsPath == null)
172         {
173             decodedAbsPath = UriParser.decode(getPath());
174         }
175 
176         return decodedAbsPath;
177     }
178 
179     /***
180      * Returns the name of the parent of the file.
181      */
182     public FileName getParent()
183     {
184         final String parentPath;
185         final int idx = getPath().lastIndexOf(SEPARATOR_CHAR);
186         if (idx == -1 || idx == getPath().length() - 1)
187         {
188             // No parent
189             return null;
190         }
191         else if (idx == 0)
192         {
193             // Root is the parent
194             parentPath = SEPARATOR;
195         }
196         else
197         {
198             parentPath = getPath().substring(0, idx);
199         }
200         return createName(parentPath, FileType.FOLDER);
201     }
202 
203     /***
204      * find the root of the filesystem
205      */
206     public FileName getRoot()
207     {
208         FileName root = this;
209         while (root.getParent() != null)
210         {
211             root = root.getParent();
212         }
213 
214         return root;
215     }
216 
217     /***
218      * Returns the URI scheme of this file.
219      */
220     public String getScheme()
221     {
222         return scheme;
223     }
224 
225     /***
226      * Returns the absolute URI of the file.
227      */
228     public String getURI()
229     {
230         if (uri == null)
231         {
232             uri = createURI();
233         }
234         return uri;
235     }
236 
237     protected String createURI()
238     {
239         final StringBuffer buffer = new StringBuffer();
240         appendRootUri(buffer, true);
241         buffer.append(getPath());
242         return buffer.toString();
243     }
244 
245     /***
246      * Converts a file name to a relative name, relative to this file name.
247      */
248     public String getRelativeName(final FileName name) throws FileSystemException
249     {
250         final String path = name.getPath();
251 
252         // Calculate the common prefix
253         final int basePathLen = getPath().length();
254         final int pathLen = path.length();
255 
256         // Deal with root
257         if (basePathLen == 1 && pathLen == 1)
258         {
259             return ".";
260         }
261         else if (basePathLen == 1)
262         {
263             return path.substring(1);
264         }
265 
266         final int maxlen = Math.min(basePathLen, pathLen);
267         int pos = 0;
268         for (; pos < maxlen && getPath().charAt(pos) == path.charAt(pos); pos++)
269         {
270         }
271 
272         if (pos == basePathLen && pos == pathLen)
273         {
274             // Same names
275             return ".";
276         }
277         else if (pos == basePathLen && pos < pathLen && path.charAt(pos) == SEPARATOR_CHAR)
278         {
279             // A descendent of the base path
280             return path.substring(pos + 1);
281         }
282 
283         // Strip the common prefix off the path
284         final StringBuffer buffer = new StringBuffer();
285         if (pathLen > 1 && (pos < pathLen || getPath().charAt(pos) != SEPARATOR_CHAR))
286         {
287             // Not a direct ancestor, need to back up
288             pos = getPath().lastIndexOf(SEPARATOR_CHAR, pos);
289             buffer.append(path.substring(pos));
290         }
291 
292         // Prepend a '../' for each element in the base path past the common
293         // prefix
294         buffer.insert(0, "..");
295         pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
296         while (pos != -1)
297         {
298             buffer.insert(0, "../");
299             pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
300         }
301 
302         return buffer.toString();
303     }
304 
305     /***
306      * Returns the root URI of the file system this file belongs to.
307      */
308     public String getRootURI()
309     {
310         if (rootUri == null)
311         {
312             final StringBuffer buffer = new StringBuffer();
313             appendRootUri(buffer, true);
314             buffer.append(SEPARATOR_CHAR);
315             rootUri = buffer.toString().intern();
316         }
317         return rootUri;
318     }
319 
320     /***
321      * Returns the depth of this file name, within its file system.
322      */
323     public int getDepth()
324     {
325         final int len = getPath().length();
326         if (len == 0 || (len == 1 && getPath().charAt(0) == SEPARATOR_CHAR))
327         {
328             return 0;
329         }
330         int depth = 1;
331         for (int pos = 0; pos > -1 && pos < len; depth++)
332         {
333             pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1);
334         }
335         return depth;
336     }
337 
338     /***
339      * Returns the extension of this file name.
340      */
341     public String getExtension()
342     {
343         if (extension == null)
344         {
345             getBaseName();
346             final int pos = baseName.lastIndexOf('.');
347             // if ((pos == -1) || (pos == baseName.length() - 1))
348             // imario@ops.co.at: Review of patch from adagoubard@chello.nl
349             // do not treat filenames like
350             // .bashrc c:\windows\.java c:\windows\.javaws c:\windows\.jedit c:\windows\.appletviewer
351             // as extension
352             if ((pos < 1) || (pos == baseName.length() - 1))
353             {
354                 // No extension
355                 extension = "";
356             }
357             else
358             {
359                 extension = baseName.substring(pos + 1).intern();
360             }
361         }
362         return extension;
363     }
364 
365     /***
366      * Determines if another file name is an ancestor of this file name.
367      */
368     public boolean isAncestor(final FileName ancestor)
369     {
370         if (!ancestor.getRootURI().equals(getRootURI()))
371         {
372             return false;
373         }
374         return checkName(ancestor.getPath(), getPath(), NameScope.DESCENDENT);
375     }
376 
377     /***
378      * Determines if another file name is a descendent of this file name.
379      */
380     public boolean isDescendent(final FileName descendent)
381     {
382         return isDescendent(descendent, NameScope.DESCENDENT);
383     }
384 
385     /***
386      * Determines if another file name is a descendent of this file name.
387      */
388     public boolean isDescendent(final FileName descendent,
389                                 final NameScope scope)
390     {
391         if (!descendent.getRootURI().equals(getRootURI()))
392         {
393             return false;
394         }
395         return checkName(getPath(), descendent.getPath(), scope);
396     }
397 
398     /***
399      * Returns the requested or current type of this name. <br />
400      * <p/>
401      * The "requested" type is the one determined during resolving the name. <br/>
402      * In this case the name is a {@link FileType#FOLDER} if it ends with an "/" else
403      * it will be a {@link FileType#FILE}<br/>
404      * </p>
405      * <p/>
406      * Once attached it will be changed to reflect the real type of this resource.
407      * </p>
408      *
409      * @return {@link FileType#FOLDER} or {@link FileType#FILE}
410      */
411     public FileType getType()
412     {
413         return type;
414     }
415 
416     /***
417      * sets the type of this file e.g. when it will be attached.
418      *
419      * @param type {@link FileType#FOLDER} or {@link FileType#FILE}
420      */
421     void setType(FileType type) throws FileSystemException
422     {
423         if (type != FileType.FOLDER && type != FileType.FILE && type != FileType.FILE_OR_FOLDER)
424         {
425             throw new FileSystemException("vfs.provider/filename-type.error");
426         }
427 
428         this.type = type;
429     }
430 
431     /***
432      * Checks whether a path fits in a particular scope of another path.
433      *
434      * @param basePath An absolute, normalised path.
435      * @param path     An absolute, normalised path.
436      */
437     public static boolean checkName(final String basePath,
438                                     final String path,
439                                     final NameScope scope)
440     {
441         if (scope == NameScope.FILE_SYSTEM)
442         {
443             // All good
444             return true;
445         }
446 
447         if (!path.startsWith(basePath))
448         {
449             return false;
450         }
451 
452         int baseLen = basePath.length();
453         if (VFS.isUriStyle())
454         {
455             // strip the trailing "/"
456             baseLen--;
457         }
458 
459         if (scope == NameScope.CHILD)
460         {
461             if (path.length() == baseLen
462                 || (baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR)
463                 || path.indexOf(SEPARATOR_CHAR, baseLen + 1) != -1)
464             {
465                 return false;
466             }
467         }
468         else if (scope == NameScope.DESCENDENT)
469         {
470             if (path.length() == baseLen
471                 || (baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR))
472             {
473                 return false;
474             }
475         }
476         else if (scope == NameScope.DESCENDENT_OR_SELF)
477         {
478             if (baseLen > 1
479                 && path.length() > baseLen
480                 && path.charAt(baseLen) != SEPARATOR_CHAR)
481             {
482                 return false;
483             }
484         }
485         else if (scope != NameScope.FILE_SYSTEM)
486         {
487             throw new IllegalArgumentException();
488         }
489 
490         return true;
491     }
492 
493     /***
494      * returns a "friendly path", this is a path without a password.
495      */
496     public String getFriendlyURI()
497     {
498         final StringBuffer buffer = new StringBuffer();
499         appendRootUri(buffer, false);
500         buffer.append(getPath());
501         return buffer.toString();
502     }
503 }