001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.fs; 020 021 import java.io.IOException; 022 import java.net.URI; 023 import java.net.URISyntaxException; 024 025 import org.apache.avro.reflect.Stringable; 026 import org.apache.commons.lang.StringUtils; 027 import org.apache.hadoop.classification.InterfaceAudience; 028 import org.apache.hadoop.classification.InterfaceStability; 029 import org.apache.hadoop.conf.Configuration; 030 031 /** Names a file or directory in a {@link FileSystem}. 032 * Path strings use slash as the directory separator. A path string is 033 * absolute if it begins with a slash. 034 */ 035 @Stringable 036 @InterfaceAudience.Public 037 @InterfaceStability.Stable 038 public class Path implements Comparable { 039 040 /** The directory separator, a slash. */ 041 public static final String SEPARATOR = "/"; 042 public static final char SEPARATOR_CHAR = '/'; 043 044 public static final String CUR_DIR = "."; 045 046 static final boolean WINDOWS 047 = System.getProperty("os.name").startsWith("Windows"); 048 049 private URI uri; // a hierarchical uri 050 051 /** Resolve a child path against a parent path. */ 052 public Path(String parent, String child) { 053 this(new Path(parent), new Path(child)); 054 } 055 056 /** Resolve a child path against a parent path. */ 057 public Path(Path parent, String child) { 058 this(parent, new Path(child)); 059 } 060 061 /** Resolve a child path against a parent path. */ 062 public Path(String parent, Path child) { 063 this(new Path(parent), child); 064 } 065 066 /** Resolve a child path against a parent path. */ 067 public Path(Path parent, Path child) { 068 // Add a slash to parent's path so resolution is compatible with URI's 069 URI parentUri = parent.uri; 070 String parentPath = parentUri.getPath(); 071 if (!(parentPath.equals("/") || parentPath.equals(""))) { 072 try { 073 parentUri = new URI(parentUri.getScheme(), parentUri.getAuthority(), 074 parentUri.getPath()+"/", null, parentUri.getFragment()); 075 } catch (URISyntaxException e) { 076 throw new IllegalArgumentException(e); 077 } 078 } 079 URI resolved = parentUri.resolve(child.uri); 080 initialize(resolved.getScheme(), resolved.getAuthority(), 081 resolved.getPath(), resolved.getFragment()); 082 } 083 084 private void checkPathArg( String path ) { 085 // disallow construction of a Path from an empty string 086 if ( path == null ) { 087 throw new IllegalArgumentException( 088 "Can not create a Path from a null string"); 089 } 090 if( path.length() == 0 ) { 091 throw new IllegalArgumentException( 092 "Can not create a Path from an empty string"); 093 } 094 } 095 096 /** Construct a path from a String. Path strings are URIs, but with 097 * unescaped elements and some additional normalization. */ 098 public Path(String pathString) { 099 checkPathArg( pathString ); 100 101 // We can't use 'new URI(String)' directly, since it assumes things are 102 // escaped, which we don't require of Paths. 103 104 // add a slash in front of paths with Windows drive letters 105 if (hasWindowsDrive(pathString, false)) 106 pathString = "/"+pathString; 107 108 // parse uri components 109 String scheme = null; 110 String authority = null; 111 112 int start = 0; 113 114 // parse uri scheme, if any 115 int colon = pathString.indexOf(':'); 116 int slash = pathString.indexOf('/'); 117 if ((colon != -1) && 118 ((slash == -1) || (colon < slash))) { // has a scheme 119 scheme = pathString.substring(0, colon); 120 start = colon+1; 121 } 122 123 // parse uri authority, if any 124 if (pathString.startsWith("//", start) && 125 (pathString.length()-start > 2)) { // has authority 126 int nextSlash = pathString.indexOf('/', start+2); 127 int authEnd = nextSlash > 0 ? nextSlash : pathString.length(); 128 authority = pathString.substring(start+2, authEnd); 129 start = authEnd; 130 } 131 132 // uri path is the rest of the string -- query & fragment not supported 133 String path = pathString.substring(start, pathString.length()); 134 135 initialize(scheme, authority, path, null); 136 } 137 138 /** 139 * Construct a path from a URI 140 */ 141 public Path(URI aUri) { 142 uri = aUri.normalize(); 143 } 144 145 /** Construct a Path from components. */ 146 public Path(String scheme, String authority, String path) { 147 checkPathArg( path ); 148 initialize(scheme, authority, path, null); 149 } 150 151 private void initialize(String scheme, String authority, String path, 152 String fragment) { 153 try { 154 this.uri = new URI(scheme, authority, normalizePath(path), null, fragment) 155 .normalize(); 156 } catch (URISyntaxException e) { 157 throw new IllegalArgumentException(e); 158 } 159 } 160 161 private String normalizePath(String path) { 162 // remove double slashes & backslashes 163 path = StringUtils.replace(path, "//", "/"); 164 if (Path.WINDOWS) { 165 path = StringUtils.replace(path, "\\", "/"); 166 } 167 168 // trim trailing slash from non-root path (ignoring windows drive) 169 int minLength = hasWindowsDrive(path, true) ? 4 : 1; 170 if (path.length() > minLength && path.endsWith("/")) { 171 path = path.substring(0, path.length()-1); 172 } 173 174 return path; 175 } 176 177 private boolean hasWindowsDrive(String path, boolean slashed) { 178 if (!WINDOWS) return false; 179 int start = slashed ? 1 : 0; 180 return 181 path.length() >= start+2 && 182 (slashed ? path.charAt(0) == '/' : true) && 183 path.charAt(start+1) == ':' && 184 ((path.charAt(start) >= 'A' && path.charAt(start) <= 'Z') || 185 (path.charAt(start) >= 'a' && path.charAt(start) <= 'z')); 186 } 187 188 189 /** Convert this to a URI. */ 190 public URI toUri() { return uri; } 191 192 /** Return the FileSystem that owns this Path. */ 193 public FileSystem getFileSystem(Configuration conf) throws IOException { 194 return FileSystem.get(this.toUri(), conf); 195 } 196 197 /** 198 * Is an absolute path (ie a slash relative path part) 199 * AND a scheme is null AND authority is null. 200 */ 201 public boolean isAbsoluteAndSchemeAuthorityNull() { 202 return (isUriPathAbsolute() && 203 uri.getScheme() == null && uri.getAuthority() == null); 204 } 205 206 /** 207 * True if the path component (i.e. directory) of this URI is absolute. 208 */ 209 public boolean isUriPathAbsolute() { 210 int start = hasWindowsDrive(uri.getPath(), true) ? 3 : 0; 211 return uri.getPath().startsWith(SEPARATOR, start); 212 } 213 214 /** True if the path component of this URI is absolute. */ 215 /** 216 * There is some ambiguity here. An absolute path is a slash 217 * relative name without a scheme or an authority. 218 * So either this method was incorrectly named or its 219 * implementation is incorrect. This method returns true 220 * even if there is a scheme and authority. 221 */ 222 public boolean isAbsolute() { 223 return isUriPathAbsolute(); 224 } 225 226 /** 227 * @return true if and only if this path represents the root of a file system 228 */ 229 public boolean isRoot() { 230 return getParent() == null; 231 } 232 233 /** Returns the final component of this path.*/ 234 public String getName() { 235 String path = uri.getPath(); 236 int slash = path.lastIndexOf(SEPARATOR); 237 return path.substring(slash+1); 238 } 239 240 /** Returns the parent of a path or null if at root. */ 241 public Path getParent() { 242 String path = uri.getPath(); 243 int lastSlash = path.lastIndexOf('/'); 244 int start = hasWindowsDrive(path, true) ? 3 : 0; 245 if ((path.length() == start) || // empty path 246 (lastSlash == start && path.length() == start+1)) { // at root 247 return null; 248 } 249 String parent; 250 if (lastSlash==-1) { 251 parent = CUR_DIR; 252 } else { 253 int end = hasWindowsDrive(path, true) ? 3 : 0; 254 parent = path.substring(0, lastSlash==end?end+1:lastSlash); 255 } 256 return new Path(uri.getScheme(), uri.getAuthority(), parent); 257 } 258 259 /** Adds a suffix to the final name in the path.*/ 260 public Path suffix(String suffix) { 261 return new Path(getParent(), getName()+suffix); 262 } 263 264 @Override 265 public String toString() { 266 // we can't use uri.toString(), which escapes everything, because we want 267 // illegal characters unescaped in the string, for glob processing, etc. 268 StringBuilder buffer = new StringBuilder(); 269 if (uri.getScheme() != null) { 270 buffer.append(uri.getScheme()); 271 buffer.append(":"); 272 } 273 if (uri.getAuthority() != null) { 274 buffer.append("//"); 275 buffer.append(uri.getAuthority()); 276 } 277 if (uri.getPath() != null) { 278 String path = uri.getPath(); 279 if (path.indexOf('/')==0 && 280 hasWindowsDrive(path, true) && // has windows drive 281 uri.getScheme() == null && // but no scheme 282 uri.getAuthority() == null) // or authority 283 path = path.substring(1); // remove slash before drive 284 buffer.append(path); 285 } 286 if (uri.getFragment() != null) { 287 buffer.append("#"); 288 buffer.append(uri.getFragment()); 289 } 290 return buffer.toString(); 291 } 292 293 @Override 294 public boolean equals(Object o) { 295 if (!(o instanceof Path)) { 296 return false; 297 } 298 Path that = (Path)o; 299 return this.uri.equals(that.uri); 300 } 301 302 @Override 303 public int hashCode() { 304 return uri.hashCode(); 305 } 306 307 @Override 308 public int compareTo(Object o) { 309 Path that = (Path)o; 310 return this.uri.compareTo(that.uri); 311 } 312 313 /** Return the number of elements in this path. */ 314 public int depth() { 315 String path = uri.getPath(); 316 int depth = 0; 317 int slash = path.length()==1 && path.charAt(0)=='/' ? -1 : 0; 318 while (slash != -1) { 319 depth++; 320 slash = path.indexOf(SEPARATOR, slash+1); 321 } 322 return depth; 323 } 324 325 /** 326 * Returns a qualified path object. 327 * 328 * Deprecated - use {@link #makeQualified(URI, Path)} 329 */ 330 @Deprecated 331 public Path makeQualified(FileSystem fs) { 332 return makeQualified(fs.getUri(), fs.getWorkingDirectory()); 333 } 334 335 /** Returns a qualified path object. */ 336 @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) 337 public Path makeQualified(URI defaultUri, Path workingDir ) { 338 Path path = this; 339 if (!isAbsolute()) { 340 path = new Path(workingDir, this); 341 } 342 343 URI pathUri = path.toUri(); 344 345 String scheme = pathUri.getScheme(); 346 String authority = pathUri.getAuthority(); 347 String fragment = pathUri.getFragment(); 348 349 if (scheme != null && 350 (authority != null || defaultUri.getAuthority() == null)) 351 return path; 352 353 if (scheme == null) { 354 scheme = defaultUri.getScheme(); 355 } 356 357 if (authority == null) { 358 authority = defaultUri.getAuthority(); 359 if (authority == null) { 360 authority = ""; 361 } 362 } 363 364 URI newUri = null; 365 try { 366 newUri = new URI(scheme, authority , 367 normalizePath(pathUri.getPath()), null, fragment); 368 } catch (URISyntaxException e) { 369 throw new IllegalArgumentException(e); 370 } 371 return new Path(newUri); 372 } 373 }