package android.net; import java.util.List; public class StringUri extends AbstractHierarchicalUri { /** Used in parcelling. */ static final int TYPE_ID = 1; /** URI string representation. */ private final String uriString; public StringUri(String uriString) { if (uriString == null) { throw new NullPointerException("uriString"); } this.uriString = uriString; } public int describeContents() { return 0; } /** Cached scheme separator index. */ private volatile int cachedSsi = NOT_CALCULATED; /** Finds the first ':'. Returns -1 if none found. */ private int findSchemeSeparator() { return cachedSsi == NOT_CALCULATED ? cachedSsi = uriString.indexOf(':') : cachedSsi; } /** Cached fragment separator index. */ private volatile int cachedFsi = NOT_CALCULATED; /** Finds the first '#'. Returns -1 if none found. */ private int findFragmentSeparator() { return cachedFsi == NOT_CALCULATED ? cachedFsi = uriString.indexOf('#', findSchemeSeparator()) : cachedFsi; } public boolean isHierarchical() { int ssi = findSchemeSeparator(); if (ssi == NOT_FOUND) { // All relative URIs are hierarchical. return true; } if (uriString.length() == ssi + 1) { // No ssp. return false; } // If the ssp starts with a '/', this is hierarchical. return uriString.charAt(ssi + 1) == '/'; } public boolean isRelative() { // Note: We return true if the index is 0 return findSchemeSeparator() == NOT_FOUND; } private volatile String scheme = NOT_CACHED; public String getScheme() { @SuppressWarnings("StringEquality") boolean cached = (scheme != NOT_CACHED); return cached ? scheme : (scheme = parseScheme()); } private String parseScheme() { int ssi = findSchemeSeparator(); return ssi == NOT_FOUND ? null : uriString.substring(0, ssi); } private Part ssp; private Part getSsp() { return ssp == null ? ssp = Part.fromEncoded(parseSsp()) : ssp; } public String getEncodedSchemeSpecificPart() { return getSsp().getEncoded(); } public String getSchemeSpecificPart() { return getSsp().getDecoded(); } private String parseSsp() { int ssi = findSchemeSeparator(); int fsi = findFragmentSeparator(); // Return everything between ssi and fsi. return fsi == NOT_FOUND ? uriString.substring(ssi + 1) : uriString.substring(ssi + 1, fsi); } private Part authority; private Part getAuthorityPart() { if (authority == null) { String encodedAuthority = parseAuthority(this.uriString, findSchemeSeparator()); return authority = Part.fromEncoded(encodedAuthority); } return authority; } public String getEncodedAuthority() { return getAuthorityPart().getEncoded(); } public String getAuthority() { return getAuthorityPart().getDecoded(); } private PathPart path; private PathPart getPathPart() { return path == null ? path = PathPart.fromEncoded(parsePath()) : path; } public String getPath() { return getPathPart().getDecoded(); } public String getEncodedPath() { return getPathPart().getEncoded(); } public List<String> getPathSegments() { return getPathPart().getPathSegments(); } private String parsePath() { String uriString = this.uriString; int ssi = findSchemeSeparator(); // If the URI is absolute. if (ssi > -1) { // Is there anything after the ':'? boolean schemeOnly = ssi + 1 == uriString.length(); if (schemeOnly) { // Opaque URI. return null; } // A '/' after the ':' means this is hierarchical. if (uriString.charAt(ssi + 1) != '/') { // Opaque URI. return null; } } else { // All relative URIs are hierarchical. } return parsePath(uriString, ssi); } private Part query; private Part getQueryPart() { return query == null ? query = Part.fromEncoded(parseQuery()) : query; } public String getEncodedQuery() { return getQueryPart().getEncoded(); } private String parseQuery() { // It doesn't make sense to cache this index. We only ever // calculate it once. int qsi = uriString.indexOf('?', findSchemeSeparator()); if (qsi == NOT_FOUND) { return null; } int fsi = findFragmentSeparator(); if (fsi == NOT_FOUND) { return uriString.substring(qsi + 1); } if (fsi < qsi) { // Invalid. return null; } return uriString.substring(qsi + 1, fsi); } public String getQuery() { return getQueryPart().getDecoded(); } private Part fragment; private Part getFragmentPart() { return fragment == null ? fragment = Part.fromEncoded(parseFragment()) : fragment; } public String getEncodedFragment() { return getFragmentPart().getEncoded(); } private String parseFragment() { int fsi = findFragmentSeparator(); return fsi == NOT_FOUND ? null : uriString.substring(fsi + 1); } public String getFragment() { return getFragmentPart().getDecoded(); } public String toString() { return uriString; } /** * Parses an authority out of the given URI string. * * @param uriString URI string * @param ssi scheme separator index, -1 for a relative URI * * @return the authority or null if none is found */ static String parseAuthority(String uriString, int ssi) { int length = uriString.length(); // If "//" follows the scheme separator, we have an authority. if (length > ssi + 2 && uriString.charAt(ssi + 1) == '/' && uriString.charAt(ssi + 2) == '/') { // We have an authority. // Look for the start of the path, query, or fragment, or the // end of the string. int end = ssi + 3; LOOP: while (end < length) { switch (uriString.charAt(end)) { case '/': // Start of path case '?': // Start of query case '#': // Start of fragment break LOOP; } end++; } return uriString.substring(ssi + 3, end); } else { return null; } } /** * Parses a path out of this given URI string. * * @param uriString URI string * @param ssi scheme separator index, -1 for a relative URI * * @return the path */ static String parsePath(String uriString, int ssi) { int length = uriString.length(); // Find start of path. int pathStart; if (length > ssi + 2 && uriString.charAt(ssi + 1) == '/' && uriString.charAt(ssi + 2) == '/') { // Skip over authority to path. pathStart = ssi + 3; LOOP: while (pathStart < length) { switch (uriString.charAt(pathStart)) { case '?': // Start of query case '#': // Start of fragment return ""; // Empty path. case '/': // Start of path! break LOOP; } pathStart++; } } else { // Path starts immediately after scheme separator. pathStart = ssi + 1; } // Find end of path. int pathEnd = pathStart; LOOP: while (pathEnd < length) { switch (uriString.charAt(pathEnd)) { case '?': // Start of query case '#': // Start of fragment break LOOP; } pathEnd++; } return uriString.substring(pathStart, pathEnd); } public Builder buildUpon() { if (isHierarchical()) { return new Builder() .scheme(getScheme()) .authority(getAuthorityPart()) .path(getPathPart()) .query(getQueryPart()) .fragment(getFragmentPart()); } else { return new Builder() .scheme(getScheme()) .opaquePart(getSsp()) .fragment(getFragmentPart()); } } }