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 019package org.apache.hadoop.fs; 020 021import java.io.IOException; 022import java.net.URI; 023import java.net.URISyntaxException; 024 025import org.apache.avro.reflect.Stringable; 026import org.apache.commons.lang.StringUtils; 027import org.apache.hadoop.classification.InterfaceAudience; 028import org.apache.hadoop.classification.InterfaceStability; 029import 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 038public 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 /** Returns the final component of this path.*/ 227 public String getName() { 228 String path = uri.getPath(); 229 int slash = path.lastIndexOf(SEPARATOR); 230 return path.substring(slash+1); 231 } 232 233 /** Returns the parent of a path or null if at root. */ 234 public Path getParent() { 235 String path = uri.getPath(); 236 int lastSlash = path.lastIndexOf('/'); 237 int start = hasWindowsDrive(path, true) ? 3 : 0; 238 if ((path.length() == start) || // empty path 239 (lastSlash == start && path.length() == start+1)) { // at root 240 return null; 241 } 242 String parent; 243 if (lastSlash==-1) { 244 parent = CUR_DIR; 245 } else { 246 int end = hasWindowsDrive(path, true) ? 3 : 0; 247 parent = path.substring(0, lastSlash==end?end+1:lastSlash); 248 } 249 return new Path(uri.getScheme(), uri.getAuthority(), parent); 250 } 251 252 /** Adds a suffix to the final name in the path.*/ 253 public Path suffix(String suffix) { 254 return new Path(getParent(), getName()+suffix); 255 } 256 257 public String toString() { 258 // we can't use uri.toString(), which escapes everything, because we want 259 // illegal characters unescaped in the string, for glob processing, etc. 260 StringBuilder buffer = new StringBuilder(); 261 if (uri.getScheme() != null) { 262 buffer.append(uri.getScheme()); 263 buffer.append(":"); 264 } 265 if (uri.getAuthority() != null) { 266 buffer.append("//"); 267 buffer.append(uri.getAuthority()); 268 } 269 if (uri.getPath() != null) { 270 String path = uri.getPath(); 271 if (path.indexOf('/')==0 && 272 hasWindowsDrive(path, true) && // has windows drive 273 uri.getScheme() == null && // but no scheme 274 uri.getAuthority() == null) // or authority 275 path = path.substring(1); // remove slash before drive 276 buffer.append(path); 277 } 278 if (uri.getFragment() != null) { 279 buffer.append("#"); 280 buffer.append(uri.getFragment()); 281 } 282 return buffer.toString(); 283 } 284 285 public boolean equals(Object o) { 286 if (!(o instanceof Path)) { 287 return false; 288 } 289 Path that = (Path)o; 290 return this.uri.equals(that.uri); 291 } 292 293 public int hashCode() { 294 return uri.hashCode(); 295 } 296 297 public int compareTo(Object o) { 298 Path that = (Path)o; 299 return this.uri.compareTo(that.uri); 300 } 301 302 /** Return the number of elements in this path. */ 303 public int depth() { 304 String path = uri.getPath(); 305 int depth = 0; 306 int slash = path.length()==1 && path.charAt(0)=='/' ? -1 : 0; 307 while (slash != -1) { 308 depth++; 309 slash = path.indexOf(SEPARATOR, slash+1); 310 } 311 return depth; 312 } 313 314 /** 315 * Returns a qualified path object. 316 * 317 * Deprecated - use {@link #makeQualified(URI, Path)} 318 */ 319 @Deprecated 320 public Path makeQualified(FileSystem fs) { 321 return makeQualified(fs.getUri(), fs.getWorkingDirectory()); 322 } 323 324 /** Returns a qualified path object. */ 325 @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) 326 public Path makeQualified(URI defaultUri, Path workingDir ) { 327 Path path = this; 328 if (!isAbsolute()) { 329 path = new Path(workingDir, this); 330 } 331 332 URI pathUri = path.toUri(); 333 334 String scheme = pathUri.getScheme(); 335 String authority = pathUri.getAuthority(); 336 String fragment = pathUri.getFragment(); 337 338 if (scheme != null && 339 (authority != null || defaultUri.getAuthority() == null)) 340 return path; 341 342 if (scheme == null) { 343 scheme = defaultUri.getScheme(); 344 } 345 346 if (authority == null) { 347 authority = defaultUri.getAuthority(); 348 if (authority == null) { 349 authority = ""; 350 } 351 } 352 353 URI newUri = null; 354 try { 355 newUri = new URI(scheme, authority , 356 normalizePath(pathUri.getPath()), null, fragment); 357 } catch (URISyntaxException e) { 358 throw new IllegalArgumentException(e); 359 } 360 return new Path(newUri); 361 } 362}