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 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 public String toString() {
265 // we can't use uri.toString(), which escapes everything, because we want
266 // illegal characters unescaped in the string, for glob processing, etc.
267 StringBuilder buffer = new StringBuilder();
268 if (uri.getScheme() != null) {
269 buffer.append(uri.getScheme());
270 buffer.append(":");
271 }
272 if (uri.getAuthority() != null) {
273 buffer.append("//");
274 buffer.append(uri.getAuthority());
275 }
276 if (uri.getPath() != null) {
277 String path = uri.getPath();
278 if (path.indexOf('/')==0 &&
279 hasWindowsDrive(path, true) && // has windows drive
280 uri.getScheme() == null && // but no scheme
281 uri.getAuthority() == null) // or authority
282 path = path.substring(1); // remove slash before drive
283 buffer.append(path);
284 }
285 if (uri.getFragment() != null) {
286 buffer.append("#");
287 buffer.append(uri.getFragment());
288 }
289 return buffer.toString();
290 }
291
292 public boolean equals(Object o) {
293 if (!(o instanceof Path)) {
294 return false;
295 }
296 Path that = (Path)o;
297 return this.uri.equals(that.uri);
298 }
299
300 public int hashCode() {
301 return uri.hashCode();
302 }
303
304 public int compareTo(Object o) {
305 Path that = (Path)o;
306 return this.uri.compareTo(that.uri);
307 }
308
309 /** Return the number of elements in this path. */
310 public int depth() {
311 String path = uri.getPath();
312 int depth = 0;
313 int slash = path.length()==1 && path.charAt(0)=='/' ? -1 : 0;
314 while (slash != -1) {
315 depth++;
316 slash = path.indexOf(SEPARATOR, slash+1);
317 }
318 return depth;
319 }
320
321 /**
322 * Returns a qualified path object.
323 *
324 * Deprecated - use {@link #makeQualified(URI, Path)}
325 */
326 @Deprecated
327 public Path makeQualified(FileSystem fs) {
328 return makeQualified(fs.getUri(), fs.getWorkingDirectory());
329 }
330
331 /** Returns a qualified path object. */
332 @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
333 public Path makeQualified(URI defaultUri, Path workingDir ) {
334 Path path = this;
335 if (!isAbsolute()) {
336 path = new Path(workingDir, this);
337 }
338
339 URI pathUri = path.toUri();
340
341 String scheme = pathUri.getScheme();
342 String authority = pathUri.getAuthority();
343 String fragment = pathUri.getFragment();
344
345 if (scheme != null &&
346 (authority != null || defaultUri.getAuthority() == null))
347 return path;
348
349 if (scheme == null) {
350 scheme = defaultUri.getScheme();
351 }
352
353 if (authority == null) {
354 authority = defaultUri.getAuthority();
355 if (authority == null) {
356 authority = "";
357 }
358 }
359
360 URI newUri = null;
361 try {
362 newUri = new URI(scheme, authority ,
363 normalizePath(pathUri.getPath()), null, fragment);
364 } catch (URISyntaxException e) {
365 throw new IllegalArgumentException(e);
366 }
367 return new Path(newUri);
368 }
369 }