tachyon.TachyonURI.java Source code

Java tutorial

Introduction

Here is the source code for tachyon.TachyonURI.java

Source

/*
 * Licensed to the University of California, Berkeley under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package tachyon;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;

import org.apache.commons.lang.StringUtils;

import tachyon.util.OSUtils;

/**
 * It uses a hierarchical URI internally. URI requires that String is escaped, TachyonURI does not.
 *
 * Does not support fragment or query in the URI.
 */
public final class TachyonURI implements Comparable<TachyonURI> {
    public static final String SEPARATOR = "/";
    public static final String CUR_DIR = ".";
    public static final String WILDCARD = "*";

    public static final TachyonURI EMPTY_URI = new TachyonURI("");

    private static final boolean WINDOWS = OSUtils.isWindows();

    // a hierarchical uri
    private final URI mUri;

    /**
     * Construct a TachyonURI from a String. Path strings are URIs, but with unescaped elements and
     * some additional normalization.
     *
     * @param pathStr path to construct the TachyonURI from
     */
    public TachyonURI(String pathStr) {
        if (pathStr == null) {
            throw new IllegalArgumentException("Can not create a uri with a null path.");
        }

        // add a slash in front of paths with Windows drive letters
        if (hasWindowsDrive(pathStr, false)) {
            pathStr = "/" + pathStr;
        }

        // parse uri components
        String scheme = null;
        String authority = null;

        int start = 0;

        // parse uri scheme, if any
        int colon = pathStr.indexOf(':');
        int slash = pathStr.indexOf('/');
        if ((colon != -1) && ((slash == -1) || (colon < slash))) { // has a scheme
            scheme = pathStr.substring(0, colon);
            start = colon + 1;
        }

        // parse uri authority, if any
        if (pathStr.startsWith("//", start) && (pathStr.length() - start > 2)) { // has authority
            int nextSlash = pathStr.indexOf('/', start + 2);
            int authEnd = nextSlash > 0 ? nextSlash : pathStr.length();
            authority = pathStr.substring(start + 2, authEnd);
            start = authEnd;
        }

        // uri path is the rest of the string -- query & fragment not supported
        String path = pathStr.substring(start, pathStr.length());

        mUri = createURI(scheme, authority, path);
    }

    /**
     * Construct a TachyonURI from components.
     *
     * @param scheme the scheme of the path. e.g. tachyon, hdfs, s3, file, null, etc
     * @param authority the authority of the path. e.g. localhost:19998, 203.1.2.5:8080
     * @param path the path component of the URI. e.g. /abc/c.txt, /a b/c/c.txt
     */
    public TachyonURI(String scheme, String authority, String path) {
        if (path == null) {
            throw new IllegalArgumentException("Can not create a uri with a null path.");
        }
        mUri = createURI(scheme, authority, path);
    }

    /**
     * Resolve a child TachyonURI against a parent TachyonURI.
     *
     * @param parent the parent
     * @param child the child
     */
    public TachyonURI(TachyonURI parent, TachyonURI child) {
        // Add a slash to parent's path so resolution is compatible with URI's
        URI parentUri = parent.mUri;
        String parentPath = parentUri.getPath();
        if (!parentPath.endsWith(SEPARATOR) && parentPath.length() > 0) {
            parentPath += SEPARATOR;
        }
        try {
            parentUri = new URI(parentUri.getScheme(), parentUri.getAuthority(), parentPath, null, null);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e);
        }
        URI resolved = parentUri.resolve(child.mUri);
        mUri = createURI(resolved.getScheme(), resolved.getAuthority(), resolved.getPath());
    }

    @Override
    public int compareTo(TachyonURI other) {
        return mUri.compareTo(other.mUri);
    }

    /**
     * Create the internal URI. Called by all constructors.
     *
     * @param scheme the scheme of the path. e.g. tachyon, hdfs, s3, file, null, etc
     * @param authority the authority of the path. e.g. localhost:19998, 203.1.2.5:8080
     * @param path the path component of the URI. e.g. /abc/c.txt, /a b/c/c.txt
     * @throws IllegalArgumentException when an illegal argument is encountered
     */
    private URI createURI(String scheme, String authority, String path) throws IllegalArgumentException {
        try {
            return new URI(scheme, authority, normalizePath(path), null, null).normalize();
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof TachyonURI)) {
            return false;
        }
        TachyonURI that = (TachyonURI) o;
        return mUri.equals(that.mUri);
    }

    /**
     * Gets the authority of the TachyonURI.
     *
     * @return the authority, null if it does not have one
     */
    public String getAuthority() {
        return mUri.getAuthority();
    }

    /**
     * Return the number of elements of the path component of the TachyonURI.
     *
     * <pre>
     * /                                  = 0
     * /a                                 = 1
     * /a/b/c.txt                         = 3
     * /a/b/                              = 3
     * a/b                                = 2
     * a\b                                = 2
     * tachyon://localhost:1998/          = 0
     * tachyon://localhost:1998/a         = 1
     * tachyon://localhost:1998/a/b.txt   = 2
     * C:\a                               = 1
     * C:                                 = 0
     * </pre>
     *
     * @return the depth
     */
    public int getDepth() {
        String path = mUri.getPath();
        if (path.isEmpty()) {
            return 0;
        }
        int depth = 0;
        int slash = path.length() == 1 && path.charAt(0) == '/' ? -1 : 0;
        while (slash != -1) {
            depth++;
            slash = path.indexOf(SEPARATOR, slash + 1);
        }
        return depth;
    }

    /**
     * Get the first n components of the TachyonURI path. There is no trailing separator as the path
     * will be normalized by normalizePath().
     *
     * <pre>
     * /a/b/c, 0              = /
     * /a/b/c, 1              = /a
     * /a/b/c, 2              = /a/b
     * /a/b/c, 3              = /a/b/c
     * /a/b/c, 4              = null
     * </pre>
     *
     * @param n identifies the number of path components to get
     * @return the first n path components, null if the path has less than n components
     */
    public String getLeadingPath(int n) {
        String path = mUri.getPath();
        if (n == 0 && path.indexOf(TachyonURI.SEPARATOR) == 0) { // the special case
            return TachyonURI.SEPARATOR;
        }
        int depth = getDepth();
        if (depth < n) {
            return null;
        } else if (depth == n) {
            return path;
        } else {
            String[] comp = path.split(SEPARATOR);
            return StringUtils.join(Arrays.asList(comp).subList(0, n + 1), SEPARATOR);
        }
    }

    /**
     * Whether or not the TachyonURI contains wildcard(s).
     *
     * @return <code>boolean</code> that indicates whether the TachyonURI contains wildcard(s)
     */
    public boolean containsWildcard() {
        return mUri.getPath().contains(WILDCARD);
    }

    /**
     * Gets the host of the TachyonURI.
     *
     * @return the host, null if it does not have one
     */
    public String getHost() {
        return mUri.getHost();
    }

    /**
     * Get the final component of the TachyonURI.
     *
     * @return the final component of the TachyonURI
     */
    public String getName() {
        String path = mUri.getPath();
        int slash = path.lastIndexOf(SEPARATOR);
        return path.substring(slash + 1);
    }

    /**
     * Get the parent of this TachyonURI or null if at root.
     *
     * @return the parent of this TachyonURI or null if at root
     */
    public TachyonURI getParent() {
        String path = mUri.getPath();
        int lastSlash = path.lastIndexOf('/');
        int start = hasWindowsDrive(path, true) ? 3 : 0;
        if ((path.length() == start) || // empty path
                (lastSlash == start && path.length() == start + 1)) { // at root
            return null;
        }
        String parent;
        if (lastSlash == -1) {
            parent = CUR_DIR;
        } else {
            int end = hasWindowsDrive(path, true) ? 3 : 0;
            parent = path.substring(0, lastSlash == end ? end + 1 : lastSlash);
        }
        return new TachyonURI(mUri.getScheme(), mUri.getAuthority(), parent);
    }

    /**
     * Gets the path component of the TachyonURI.
     *
     * @return the path
     */
    public String getPath() {
        return mUri.getPath();
    }

    /**
     * Gets the port of the TachyonURI.
     *
     * @return the port, -1 if it does not have one
     */
    public int getPort() {
        return mUri.getPort();
    }

    /**
     * Get the scheme of the TachyonURI.
     *
     * @return the scheme, null if there is no scheme
     */
    public String getScheme() {
        return mUri.getScheme();
    }

    /**
     * Tells if the TachyonURI has authority or not.
     *
     * @return true if it has, false otherwise
     */
    public boolean hasAuthority() {
        return mUri.getAuthority() != null;
    }

    @Override
    public int hashCode() {
        return mUri.hashCode();
    }

    /**
     * Tells if this TachyonURI has scheme or not.
     *
     * @return true if it has, false otherwise
     */
    public boolean hasScheme() {
        return mUri.getScheme() != null;
    }

    /**
     * Check if the path is a windows path.
     *
     * @param path the path to check
     * @param slashed if the path starts with a slash
     * @return true if it is a windows path, false otherwise
     */
    private boolean hasWindowsDrive(String path, boolean slashed) {
        int start = slashed ? 1 : 0;
        return WINDOWS && path.length() >= start + 2 && (!slashed || path.charAt(0) == '/')
                && path.charAt(start + 1) == ':' && ((path.charAt(start) >= 'A' && path.charAt(start) <= 'Z')
                        || (path.charAt(start) >= 'a' && path.charAt(start) <= 'z'));
    }

    /**
     * Tells whether or not the TachyonURI is absolute.
     *
     * <p>
     * A TachyonURI is absolute if, and only if, it has a scheme component.
     * </p>
     *
     * @return <tt>true</tt> if, and only if, this TachyonURI is absolute
     */
    public boolean isAbsolute() {
        return mUri.isAbsolute();
    }

    /**
     * Tells whether or not the path component of the TachyonURI is absolute.
     *
     * <p>
     * A path is absolute if, and only if, it starts with root.
     * </p>
     *
     * @return <tt>true</tt> if, and only if, the TachyonURI's path component is absolute
     */
    public boolean isPathAbsolute() {
        int start = hasWindowsDrive(mUri.getPath(), true) ? 3 : 0;
        return mUri.getPath().startsWith(SEPARATOR, start);
    }

    /**
     * Tells whether or not the TachyonURI is root.
     *
     * <p>
     * A URI is root if its path equals to "/"
     * </p>
     *
     * @return <tt>true</tt> if, and only if, this URI is root
     */
    public boolean isRoot() {
        return mUri.getPath().equals(SEPARATOR) || (mUri.getPath().isEmpty() && mUri.getAuthority() != null);
    }

    /**
     * Append additional path elements to the end of a TachyonURI.
     *
     * @param suffix the suffix to add
     * @return the new TachyonURI
     */
    public TachyonURI join(String suffix) {
        return new TachyonURI(getScheme(), getAuthority(), getPath() + TachyonURI.SEPARATOR + suffix);
    }

    /**
     * Append additional path elements to the end of a TachyonURI.
     *
     * @param suffix the suffix to add
     * @return the new TachyonURI
     */
    public TachyonURI join(TachyonURI suffix) {
        return join(suffix.toString());
    }

    /**
     * Normalize the path component of the TachyonURI, by replacing all "//" and "\\" with "/", and
     * trimming trailing slash from non-root path (ignoring windows drive).
     *
     * @param path the path to normalize
     * @return the normalized path
     */
    private String normalizePath(String path) {
        while (path.contains("\\")) {
            path = path.replace("\\", "/");
        }
        while (path.contains("//")) {
            path = path.replace("//", "/");
        }

        int minLength = hasWindowsDrive(path, true) ? 4 : 1;
        while (path.length() > minLength && path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }

        return path;
    }

    /**
     * Illegal characters unescaped in the string, for glob processing, etc.
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (mUri.getScheme() != null) {
            sb.append(mUri.getScheme());
            sb.append("://");
        }
        if (mUri.getAuthority() != null) {
            if (mUri.getScheme() == null) {
                sb.append("//");
            }
            sb.append(mUri.getAuthority());
        }
        if (mUri.getPath() != null) {
            String path = mUri.getPath();
            if (path.indexOf('/') == 0 && hasWindowsDrive(path, true) && // has windows drive
                    mUri.getScheme() == null && // but no scheme
                    mUri.getAuthority() == null) { // or authority
                path = path.substring(1); // remove slash before drive
            }
            sb.append(path);
        }
        return sb.toString();
    }
}