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