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    }