/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio; import alluxio.collections.Pair; import com.google.common.base.Preconditions; import java.io.Serializable; import java.net.URISyntaxException; /** * This interface represents a URI. This {@link URI} supports more than just strict * {@link java.net.URI}. Some examples: * * Windows paths * * C:\ * * D:\path\to\file * * E:\path\to\skip\..\file * * URI with multiple scheme components * * scheme://host:123/path * * scheme:part2//host:123/path * * scheme:part2://host:123/path * * scheme:part2:part3//host:123/path * * scheme:part2:part3://host:123/path * * Currently, does not support fragment in the URI. */ public interface URI extends Comparable<URI>, Serializable { /** * Factory for {@link URI}. */ class Factory { private Factory() {} // prevent instantiation /** * Creates a {@link URI} from a string. * * @param uriStr URI string to create the {@link URI} from * @return the created {@link URI} */ public static URI create(String uriStr) { Preconditions.checkArgument(uriStr != null, "Can not create a uri with a null path."); // add a slash in front of paths with Windows drive letters if (AlluxioURI.hasWindowsDrive(uriStr, false)) { uriStr = "/" + uriStr; } // parse uri components String scheme = null; String authority = null; String query = null; int start = 0; // parse uri scheme, if any int colon = uriStr.indexOf(':'); int slash = uriStr.indexOf('/'); if ((colon != -1) && ((slash == -1) || (colon < slash))) { // has a scheme if (slash != -1) { // There is a slash. The scheme may have multiple parts, so the scheme is everything // before the slash. start = slash; // Ignore any trailing colons from the scheme. while (slash > 0 && uriStr.charAt(slash - 1) == ':') { slash--; } scheme = uriStr.substring(0, slash); } else { // There is no slash. The scheme is the component before the first colon. scheme = uriStr.substring(0, colon); start = colon + 1; } } // parse uri authority, if any if (uriStr.startsWith("//", start) && (uriStr.length() - start > 2)) { // has authority int nextSlash = uriStr.indexOf('/', start + 2); int authEnd = nextSlash > 0 ? nextSlash : uriStr.length(); authority = uriStr.substring(start + 2, authEnd); start = authEnd; } // uri path is the rest of the string -- fragment not supported String path = uriStr.substring(start, uriStr.length()); // Parse the query part. int question = path.indexOf('?'); if (question != -1) { // There is a query. query = path.substring(question + 1); path = path.substring(0, question); } return create(scheme, authority, path, query); } /** * Creates a {@link URI} from components. * * @param scheme the scheme string of the URI * @param authority the authority string of the URI * @param path the path component of the URI * @param query the query component of the URI * @return the created {@link URI} */ public static URI create(String scheme, String authority, String path, String query) { Preconditions.checkArgument(path != null, "Can not create a uri with a null path."); // Handle schemes with two components. Pair<String, String> schemeComponents = getSchemeComponents(scheme); String schemePrefix = schemeComponents.getFirst(); scheme = schemeComponents.getSecond(); if (scheme == null || schemePrefix.isEmpty()) { // This is a standard URI. return new StandardURI(scheme, authority, path, query); } else { return new MultiPartSchemeURI(schemePrefix, scheme, authority, path, query); } } /** * Resolves a child {@link URI} against a parent {@link URI}. * * @param parent the parent * @param child the child * @return the created {@link URI} */ public static URI create(URI parent, URI child) { // Add a slash to parent's path so resolution is compatible with URI's String parentPath = parent.getPath(); if (!parentPath.endsWith(AlluxioURI.SEPARATOR) && parentPath.length() > 0) { parentPath += AlluxioURI.SEPARATOR; } java.net.URI parentUri; java.net.URI childUri; try { // To be compatible with URI, must use the last component of the scheme. parentUri = new java.net.URI(getSchemeComponents(parent.getScheme()).getSecond(), parent.getAuthority(), parentPath, parent.getQuery(), null); childUri = new java.net.URI(getSchemeComponents(child.getScheme()).getSecond(), child.getAuthority(), child.getPath(), child.getQuery(), null); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } java.net.URI resolved = parentUri.resolve(childUri); String resolvedScheme; // Determine which scheme prefix to take. if (child.getPath() == null || parent.getPath() == null || child.getScheme() != null) { // With these conditions, the resolved URI uses the child's scheme. resolvedScheme = child.getScheme(); } else { // Otherwise, the parent scheme is used in the resolved URI. resolvedScheme = parent.getScheme(); } return create(resolvedScheme, resolved.getAuthority(), resolved.getPath(), resolved.getQuery()); } /** * Returns a {@link Pair} of components of the given scheme. A given scheme may have have two * components if it has the ':' character to specify a sub-protocol of the scheme. If the * scheme does not have multiple components, the first component will be the empty string, and * the second component will be the given scheme. If the given scheme is null, both components * in the {@link Pair} will be null. * * @param scheme the scheme string * @return a {@link Pair} with the scheme components */ private static Pair<String, String> getSchemeComponents(String scheme) { if (scheme == null) { return new Pair<>(null, null); } int colon = scheme.lastIndexOf(':'); if (colon == -1) { return new Pair<>("", scheme); } return new Pair<>(scheme.substring(0, colon), scheme.substring(colon + 1)); } } /** * @return the authority of the {@link URI}, null if it does not have one */ String getAuthority(); /** * @return the host of the {@link URI}, null if it does not have one */ String getHost(); /** * @return the path of the {@link URI} */ String getPath(); /** * @return the query component of the {@link URI} */ String getQuery(); /** * @return the port of the {@link URI}, -1 if it does not have one */ int getPort(); /** * @return the scheme of the {@link URI}, null if there is no scheme */ String getScheme(); /** * Tells whether or not the {@link URI} is absolute. * * <p> * An {@link URI} is absolute if, and only if, it has a scheme component. * </p> * * @return <tt>true</tt> if, and only if, this {@link URI} is absolute */ boolean isAbsolute(); /** * @return the {@link java.net.URI} representation of this {@link URI} */ java.net.URI getBaseURI(); }