/*
* Licensed to the Apache Software Foundation (ASF) 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;
/**
* 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 class TachyonURI implements Comparable<TachyonURI> {
public static final String SEPARATOR = "/";
public static final String CUR_DIR = ".";
private static final boolean WINDOWS = System.getProperty("os.name").startsWith("Windows");
// a hierarchical uri
private URI mUri;
/**
* Construct a path from a String. Path strings are URIs, but with unescaped elements and some
* additional normalization.
*/
public TachyonURI(String pathStr) {
if (pathStr == null || pathStr.length() == 0) {
throw new IllegalArgumentException("Can not create a Path from a null or empty string");
}
// 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());
initialize(scheme, authority, path);
}
/**
* Construct a Tachyon URI 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 || path.length() == 0) {
throw new IllegalArgumentException("Can not create a Path from a null or empty string");
}
initialize(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);
initialize(resolved.getScheme(), resolved.getAuthority(), resolved.getPath());
}
@Override
public int compareTo(TachyonURI other) {
return mUri.compareTo(other.mUri);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TachyonURI)) {
return false;
}
return mUri.equals(((TachyonURI) o).mUri);
}
/**
* Gets the authority of this 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
* C:\a -> 1
* C: -> 0
* tachyon://localhost:1998/ -> 0
* tachyon://localhost:1998/a -> 1
* tachyon://localhost:1998/a/b.txt -> 2
* </pre>
*
* @return the depth
*/
public int getDepth() {
String path = mUri.getPath();
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;
}
/**
* Gets the host of this 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 part component of this TachyonURI.
*
* @return the path.
*/
public String getPath() {
return mUri.getPath();
}
/**
* Gets the port of this TachyonURI.
*
* @return the port, -1 if it does not have one.
*/
public int getPort() {
return mUri.getPort();
}
/**
* Get the scheme of the Tachyon URI.
*
* @return the scheme, null if there is no scheme.
*/
public String getScheme() {
return mUri.getScheme();
}
/**
* Tells if this 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) == '/' : true)
&& path.charAt(start + 1) == ':'
&& ((path.charAt(start) >= 'A' && path.charAt(start) <= 'Z') || (path.charAt(start) >= 'a' && path
.charAt(start) <= 'z'));
}
/**
* Initialize the class instance. 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
*/
private void initialize(String scheme, String authority, String path) {
try {
mUri = new URI(scheme, authority, normalizePath(path), null, null).normalize();
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Tells whether or not this URI is absolute.
*
* <p>
* A URI is absolute if, and only if, it has a scheme component.
* </p>
*
* @return <tt>true</tt> if, and only if, this URI is absolute
*/
public boolean isAbsolute() {
return mUri.isAbsolute();
}
/**
* Tells whether or not the path component of this 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, this TachyonURI's path component is absolute
*/
public boolean isPathAbsolute() {
int start = hasWindowsDrive(mUri.getPath(), true) ? 3 : 0;
return mUri.getPath().startsWith(SEPARATOR, start);
}
/**
* Add a suffix to the end of the Tachyon URI.
*
* @param suffix
* the suffix to add
* @return the new TachyonURI
*/
public TachyonURI join(String suffix) {
return new TachyonURI(toString() + suffix);
}
/**
* Add a suffix to the end of the Tachyon URI.
*
* @param TachyonURI
* the suffix to add
* @return the new TachyonURI
*/
public TachyonURI join(TachyonURI suffix) {
return new TachyonURI(toString() + 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
* @return
*/
private String normalizePath(String path) {
while (path.indexOf("//") != -1) {
path = path.replace("//", "/");
}
while (path.indexOf("\\") != -1) {
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() {
StringBuffer sb = new StringBuffer();
if (mUri.getScheme() != null) {
sb.append(mUri.getScheme());
sb.append(":");
}
if (mUri.getAuthority() != 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();
}
}