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 */ 017package org.apache.commons.vfs2.provider; 018 019import org.apache.commons.vfs2.FileName; 020import org.apache.commons.vfs2.FileSystemException; 021import org.apache.commons.vfs2.FileType; 022import org.apache.commons.vfs2.NameScope; 023import org.apache.commons.vfs2.VFS; 024 025/** 026 * A default file name implementation. 027 */ 028public abstract class AbstractFileName implements FileName { 029 030 private final String scheme; 031 private final String absPath; 032 private FileType type; 033 034 // Cached stuff 035 private String uri; 036 private String baseName; 037 private String rootUri; 038 private String extension; 039 private String decodedAbsPath; 040 041 private String key = null; 042 043 public AbstractFileName(final String scheme, final String absPath, final FileType type) { 044 this.rootUri = null; 045 this.scheme = scheme; 046 this.type = type; 047 if (absPath != null && absPath.length() > 0) { 048 if (absPath.length() > 1 && absPath.endsWith("/")) { 049 this.absPath = absPath.substring(0, absPath.length() - 1); 050 } else { 051 this.absPath = absPath; 052 } 053 } else { 054 this.absPath = ROOT_PATH; 055 } 056 } 057 058 @Override 059 public boolean equals(final Object o) { 060 if (this == o) { 061 return true; 062 } 063 if (o == null || getClass() != o.getClass()) { 064 return false; 065 } 066 067 final AbstractFileName that = (AbstractFileName) o; 068 069 return getKey().equals(that.getKey()); 070 } 071 072 @Override 073 public int hashCode() { 074 return getKey().hashCode(); 075 } 076 077 /** 078 * Implement Comparable. 079 * 080 * @param obj another abstract filename 081 * @return negative number if less than, 0 if equal, positive if greater than. 082 */ 083 @Override 084 public int compareTo(final FileName obj) { 085 final AbstractFileName name = (AbstractFileName) obj; 086 return getKey().compareTo(name.getKey()); 087 } 088 089 /** 090 * Returns the URI of the file. 091 * 092 * @return the FileName as a URI. 093 */ 094 @Override 095 public String toString() { 096 return getURI(); 097 } 098 099 /** 100 * Factory method for creating name instances. 101 * 102 * @param absolutePath The absolute path. 103 * @param fileType The FileType. 104 * @return The FileName. 105 */ 106 public abstract FileName createName(String absolutePath, FileType fileType); 107 108 /** 109 * Builds the root URI for this file name. Note that the root URI must not end with a separator character. 110 * 111 * @param buffer A StringBuilder to use to construct the URI. 112 * @param addPassword true if the password should be added, false otherwise. 113 */ 114 protected abstract void appendRootUri(StringBuilder buffer, boolean addPassword); 115 116 /** 117 * Returns the base name of the file. 118 * 119 * @return The base name of the file. 120 */ 121 @Override 122 public String getBaseName() { 123 if (baseName == null) { 124 final int idx = getPath().lastIndexOf(SEPARATOR_CHAR); 125 if (idx == -1) { 126 baseName = getPath(); 127 } else { 128 baseName = getPath().substring(idx + 1); 129 } 130 } 131 132 return baseName; 133 } 134 135 /** 136 * Returns the absolute path of the file, relative to the root of the file system that the file belongs to. 137 * 138 * @return The path String. 139 */ 140 @Override 141 public String getPath() { 142 if (VFS.isUriStyle()) { 143 return absPath + getUriTrailer(); 144 } 145 return absPath; 146 } 147 148 protected String getUriTrailer() { 149 return getType().hasChildren() ? "/" : ""; 150 } 151 152 /** 153 * Returns the decoded path. 154 * 155 * @return The decoded path String. 156 * @throws FileSystemException If an error occurs. 157 */ 158 @Override 159 public String getPathDecoded() throws FileSystemException { 160 if (decodedAbsPath == null) { 161 decodedAbsPath = UriParser.decode(getPath()); 162 } 163 164 return decodedAbsPath; 165 } 166 167 /** 168 * Returns the name of the parent of the file. 169 * 170 * @return the FileName of the parent. 171 */ 172 @Override 173 public FileName getParent() { 174 final String parentPath; 175 final int idx = getPath().lastIndexOf(SEPARATOR_CHAR); 176 if (idx == -1 || idx == getPath().length() - 1) { 177 // No parent 178 return null; 179 } else if (idx == 0) { 180 // Root is the parent 181 parentPath = SEPARATOR; 182 } else { 183 parentPath = getPath().substring(0, idx); 184 } 185 return createName(parentPath, FileType.FOLDER); 186 } 187 188 /** 189 * find the root of the filesystem. 190 * 191 * @return The root FileName. 192 */ 193 @Override 194 public FileName getRoot() { 195 FileName root = this; 196 while (root.getParent() != null) { 197 root = root.getParent(); 198 } 199 200 return root; 201 } 202 203 /** 204 * Returns the URI scheme of this file. 205 * 206 * @return The protocol used to access the file. 207 */ 208 @Override 209 public String getScheme() { 210 return scheme; 211 } 212 213 /** 214 * Returns the absolute URI of the file. 215 * 216 * @return The absolute URI of the file. 217 */ 218 @Override 219 public String getURI() { 220 if (uri == null) { 221 uri = createURI(); 222 } 223 return uri; 224 } 225 226 protected String createURI() { 227 return createURI(false, true); 228 } 229 230 /** 231 * Create a path that does not use the FileType since that field is not immutable. 232 * 233 * @return The key. 234 */ 235 private String getKey() { 236 if (key == null) { 237 key = getURI(); 238 } 239 return key; 240 } 241 242 /** 243 * Returns the URI without a password. 244 * 245 * @return Returns the URI without a password. 246 */ 247 @Override 248 public String getFriendlyURI() { 249 return createURI(false, false); 250 } 251 252 private String createURI(final boolean useAbsolutePath, final boolean usePassword) { 253 final StringBuilder buffer = new StringBuilder(); 254 appendRootUri(buffer, usePassword); 255 buffer.append(useAbsolutePath ? absPath : getPath()); 256 return buffer.toString(); 257 } 258 259 /** 260 * Converts a file name to a relative name, relative to this file name. 261 * 262 * @param name The FileName. 263 * @return The relative path to the file. 264 * @throws FileSystemException if an error occurs. 265 */ 266 @Override 267 public String getRelativeName(final FileName name) throws FileSystemException { 268 final String path = name.getPath(); 269 270 // Calculate the common prefix 271 final int basePathLen = getPath().length(); 272 final int pathLen = path.length(); 273 274 // Deal with root 275 if (basePathLen == 1 && pathLen == 1) { 276 return "."; 277 } else if (basePathLen == 1) { 278 return path.substring(1); 279 } 280 281 final int maxlen = Math.min(basePathLen, pathLen); 282 int pos = 0; 283 for (; pos < maxlen && getPath().charAt(pos) == path.charAt(pos); pos++) { 284 } 285 286 if (pos == basePathLen && pos == pathLen) { 287 // Same names 288 return "."; 289 } else if (pos == basePathLen && pos < pathLen && path.charAt(pos) == SEPARATOR_CHAR) { 290 // A descendent of the base path 291 return path.substring(pos + 1); 292 } 293 294 // Strip the common prefix off the path 295 final StringBuilder buffer = new StringBuilder(); 296 if (pathLen > 1 && (pos < pathLen || getPath().charAt(pos) != SEPARATOR_CHAR)) { 297 // Not a direct ancestor, need to back up 298 pos = getPath().lastIndexOf(SEPARATOR_CHAR, pos); 299 buffer.append(path.substring(pos)); 300 } 301 302 // Prepend a '../' for each element in the base path past the common 303 // prefix 304 buffer.insert(0, ".."); 305 pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1); 306 while (pos != -1) { 307 buffer.insert(0, "../"); 308 pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1); 309 } 310 311 return buffer.toString(); 312 } 313 314 /** 315 * Returns the root URI of the file system this file belongs to. 316 * 317 * @return The URI of the root. 318 */ 319 @Override 320 public String getRootURI() { 321 if (rootUri == null) { 322 final StringBuilder buffer = new StringBuilder(); 323 appendRootUri(buffer, true); 324 buffer.append(SEPARATOR_CHAR); 325 rootUri = buffer.toString().intern(); 326 } 327 return rootUri; 328 } 329 330 /** 331 * Returns the depth of this file name, within its file system. 332 * 333 * @return The depth of the file name. 334 */ 335 @Override 336 public int getDepth() { 337 final int len = getPath().length(); 338 if (len == 0 || len == 1 && getPath().charAt(0) == SEPARATOR_CHAR) { 339 return 0; 340 } 341 int depth = 1; 342 for (int pos = 0; pos > -1 && pos < len; depth++) { 343 pos = getPath().indexOf(SEPARATOR_CHAR, pos + 1); 344 } 345 return depth; 346 } 347 348 /** 349 * Returns the extension of this file name. 350 * 351 * @return The file extension. 352 */ 353 @Override 354 public String getExtension() { 355 if (extension == null) { 356 getBaseName(); 357 final int pos = baseName.lastIndexOf('.'); 358 // if ((pos == -1) || (pos == baseName.length() - 1)) 359 // imario@ops.co.at: Review of patch from adagoubard@chello.nl 360 // do not treat filenames like 361 // .bashrc c:\windows\.java c:\windows\.javaws c:\windows\.jedit c:\windows\.appletviewer 362 // as extension 363 if (pos < 1 || pos == baseName.length() - 1) { 364 // No extension 365 extension = ""; 366 } else { 367 extension = baseName.substring(pos + 1).intern(); 368 } 369 } 370 return extension; 371 } 372 373 /** 374 * Determines if another file name is an ancestor of this file name. 375 * 376 * @param ancestor The FileName to check. 377 * @return true if the FileName is an ancestor, false otherwise. 378 */ 379 @Override 380 public boolean isAncestor(final FileName ancestor) { 381 if (!ancestor.getRootURI().equals(getRootURI())) { 382 return false; 383 } 384 return checkName(ancestor.getPath(), getPath(), NameScope.DESCENDENT); 385 } 386 387 /** 388 * Determines if another file name is a descendent of this file name. 389 * 390 * @param descendent The FileName to check. 391 * @return true if the FileName is a descendent, false otherwise. 392 */ 393 @Override 394 public boolean isDescendent(final FileName descendent) { 395 return isDescendent(descendent, NameScope.DESCENDENT); 396 } 397 398 /** 399 * Determines if another file name is a descendent of this file name. 400 * 401 * @param descendent The FileName to check. 402 * @param scope The NameScope. 403 * @return true if the FileName is a descendent, false otherwise. 404 */ 405 @Override 406 public boolean isDescendent(final FileName descendent, final NameScope scope) { 407 if (!descendent.getRootURI().equals(getRootURI())) { 408 return false; 409 } 410 return checkName(getPath(), descendent.getPath(), scope); 411 } 412 413 /** 414 * Checks if this file name is a name for a regular file by using its type. 415 * 416 * @return true if this file is a regular file. 417 * @throws FileSystemException may be thrown by subclasses. 418 * @see #getType() 419 * @see FileType#FILE 420 */ 421 @Override 422 public boolean isFile() throws FileSystemException { 423 // Use equals instead of == to avoid any class loader worries. 424 return FileType.FILE.equals(this.getType()); 425 } 426 427 /** 428 * Returns the requested or current type of this name. 429 * <p> 430 * The "requested" type is the one determined during resolving the name. n this case the name is a 431 * {@link FileType#FOLDER} if it ends with an "/" else it will be a {@link FileType#FILE}. 432 * <p> 433 * Once attached it will be changed to reflect the real type of this resource. 434 * 435 * @return {@link FileType#FOLDER} or {@link FileType#FILE} 436 */ 437 @Override 438 public FileType getType() { 439 return type; 440 } 441 442 /** 443 * Sets the type of this file e.g. when it will be attached. 444 * 445 * @param type {@link FileType#FOLDER} or {@link FileType#FILE} 446 * @throws FileSystemException if an error occurs. 447 */ 448 void setType(final FileType type) throws FileSystemException { 449 if (type != FileType.FOLDER && type != FileType.FILE && type != FileType.FILE_OR_FOLDER) { 450 throw new FileSystemException("vfs.provider/filename-type.error"); 451 } 452 453 this.type = type; 454 } 455 456 /** 457 * Checks whether a path fits in a particular scope of another path. 458 * 459 * @param basePath An absolute, normalised path. 460 * @param path An absolute, normalised path. 461 * @param scope The NameScope. 462 * @return true if the path fits in the scope, false otherwise. 463 */ 464 public static boolean checkName(final String basePath, final String path, final NameScope scope) { 465 if (scope == NameScope.FILE_SYSTEM) { 466 // All good 467 return true; 468 } 469 470 if (!path.startsWith(basePath)) { 471 return false; 472 } 473 474 int baseLen = basePath.length(); 475 if (VFS.isUriStyle()) { 476 // strip the trailing "/" 477 baseLen--; 478 } 479 480 if (scope == NameScope.CHILD) { 481 if (path.length() == baseLen || baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR 482 || path.indexOf(SEPARATOR_CHAR, baseLen + 1) != -1) { 483 return false; 484 } 485 } else if (scope == NameScope.DESCENDENT) { 486 if (path.length() == baseLen || baseLen > 1 && path.charAt(baseLen) != SEPARATOR_CHAR) { 487 return false; 488 } 489 } else if (scope == NameScope.DESCENDENT_OR_SELF) { 490 if (baseLen > 1 && path.length() > baseLen && path.charAt(baseLen) != SEPARATOR_CHAR) { 491 return false; 492 } 493 } else if (scope != NameScope.FILE_SYSTEM) { 494 throw new IllegalArgumentException(); 495 } 496 497 return true; 498 } 499}