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; 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 path = StringUtils.replace(path, "\\", "/"); 165 166 // trim trailing slash from non-root path (ignoring windows drive) 167 int minLength = hasWindowsDrive(path, true) ? 4 : 1; 168 if (path.length() > minLength && path.endsWith("/")) { 169 path = path.substring(0, path.length()-1); 170 } 171 172 return path; 173 } 174 175 private boolean hasWindowsDrive(String path, boolean slashed) { 176 if (!WINDOWS) return false; 177 int start = slashed ? 1 : 0; 178 return 179 path.length() >= start+2 && 180 (slashed ? path.charAt(0) == '/' : true) && 181 path.charAt(start+1) == ':' && 182 ((path.charAt(start) >= 'A' && path.charAt(start) <= 'Z') || 183 (path.charAt(start) >= 'a' && path.charAt(start) <= 'z')); 184 } 185 186 187 /** Convert this to a URI. */ 188 public URI toUri() { return uri; } 189 190 /** Return the FileSystem that owns this Path. */ 191 public FileSystem getFileSystem(Configuration conf) throws IOException { 192 return FileSystem.get(this.toUri(), conf); 193 } 194 195 /** 196 * Is an absolute path (ie a slash relative path part) 197 * AND a scheme is null AND authority is null. 198 */ 199 public boolean isAbsoluteAndSchemeAuthorityNull() { 200 return (isUriPathAbsolute() && 201 uri.getScheme() == null && uri.getAuthority() == null); 202 } 203 204 /** 205 * True if the path component (i.e. directory) of this URI is absolute. 206 */ 207 public boolean isUriPathAbsolute() { 208 int start = hasWindowsDrive(uri.getPath(), true) ? 3 : 0; 209 return uri.getPath().startsWith(SEPARATOR, start); 210 } 211 212 /** True if the path component of this URI is absolute. */ 213 /** 214 * There is some ambiguity here. An absolute path is a slash 215 * relative name without a scheme or an authority. 216 * So either this method was incorrectly named or its 217 * implementation is incorrect. This method returns true 218 * even if there is a scheme and authority. 219 */ 220 public boolean isAbsolute() { 221 return isUriPathAbsolute(); 222 } 223 224 /** Returns the final component of this path.*/ 225 public String getName() { 226 String path = uri.getPath(); 227 int slash = path.lastIndexOf(SEPARATOR); 228 return path.substring(slash+1); 229 } 230 231 /** Returns the parent of a path or null if at root. */ 232 public Path getParent() { 233 String path = uri.getPath(); 234 int lastSlash = path.lastIndexOf('/'); 235 int start = hasWindowsDrive(path, true) ? 3 : 0; 236 if ((path.length() == start) || // empty path 237 (lastSlash == start && path.length() == start+1)) { // at root 238 return null; 239 } 240 String parent; 241 if (lastSlash==-1) { 242 parent = CUR_DIR; 243 } else { 244 int end = hasWindowsDrive(path, true) ? 3 : 0; 245 parent = path.substring(0, lastSlash==end?end+1:lastSlash); 246 } 247 return new Path(uri.getScheme(), uri.getAuthority(), parent); 248 } 249 250 /** Adds a suffix to the final name in the path.*/ 251 public Path suffix(String suffix) { 252 return new Path(getParent(), getName()+suffix); 253 } 254 255 public String toString() { 256 // we can't use uri.toString(), which escapes everything, because we want 257 // illegal characters unescaped in the string, for glob processing, etc. 258 StringBuilder buffer = new StringBuilder(); 259 if (uri.getScheme() != null) { 260 buffer.append(uri.getScheme()); 261 buffer.append(":"); 262 } 263 if (uri.getAuthority() != null) { 264 buffer.append("//"); 265 buffer.append(uri.getAuthority()); 266 } 267 if (uri.getPath() != null) { 268 String path = uri.getPath(); 269 if (path.indexOf('/')==0 && 270 hasWindowsDrive(path, true) && // has windows drive 271 uri.getScheme() == null && // but no scheme 272 uri.getAuthority() == null) // or authority 273 path = path.substring(1); // remove slash before drive 274 buffer.append(path); 275 } 276 if (uri.getFragment() != null) { 277 buffer.append("#"); 278 buffer.append(uri.getFragment()); 279 } 280 return buffer.toString(); 281 } 282 283 public boolean equals(Object o) { 284 if (!(o instanceof Path)) { 285 return false; 286 } 287 Path that = (Path)o; 288 return this.uri.equals(that.uri); 289 } 290 291 public int hashCode() { 292 return uri.hashCode(); 293 } 294 295 public int compareTo(Object o) { 296 Path that = (Path)o; 297 return this.uri.compareTo(that.uri); 298 } 299 300 /** Return the number of elements in this path. */ 301 public int depth() { 302 String path = uri.getPath(); 303 int depth = 0; 304 int slash = path.length()==1 && path.charAt(0)=='/' ? -1 : 0; 305 while (slash != -1) { 306 depth++; 307 slash = path.indexOf(SEPARATOR, slash+1); 308 } 309 return depth; 310 } 311 312 /** 313 * Returns a qualified path object. 314 * 315 * Deprecated - use {@link #makeQualified(URI, Path)} 316 */ 317 @Deprecated 318 public Path makeQualified(FileSystem fs) { 319 return makeQualified(fs.getUri(), fs.getWorkingDirectory()); 320 } 321 322 /** Returns a qualified path object. */ 323 @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) 324 public Path makeQualified(URI defaultUri, Path workingDir ) { 325 Path path = this; 326 if (!isAbsolute()) { 327 path = new Path(workingDir, this); 328 } 329 330 URI pathUri = path.toUri(); 331 332 String scheme = pathUri.getScheme(); 333 String authority = pathUri.getAuthority(); 334 String fragment = pathUri.getFragment(); 335 336 if (scheme != null && 337 (authority != null || defaultUri.getAuthority() == null)) 338 return path; 339 340 if (scheme == null) { 341 scheme = defaultUri.getScheme(); 342 } 343 344 if (authority == null) { 345 authority = defaultUri.getAuthority(); 346 if (authority == null) { 347 authority = ""; 348 } 349 } 350 351 URI newUri = null; 352 try { 353 newUri = new URI(scheme, authority , 354 normalizePath(pathUri.getPath()), null, fragment); 355 } catch (URISyntaxException e) { 356 throw new IllegalArgumentException(e); 357 } 358 return new Path(newUri); 359 } 360 }