public final class

UriUtil

extends java.lang.Object

 java.lang.Object

↳androidx.media3.common.util.UriUtil

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-common', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-common
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-common:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

Utility methods for manipulating URIs.

Summary

Methods
public static java.lang.StringgetRelativePath(Uri baseUri, Uri targetUri)

Calculates the relative path from a base URI to a target URI.

public static booleanisAbsolute(java.lang.String uri)

Returns true if the URI is starting with a scheme component, false otherwise.

public static UriremoveQueryParameter(Uri uri, java.lang.String queryParameterName)

Removes query parameter from a URI, if present.

public static java.lang.Stringresolve(java.lang.String baseUri, java.lang.String referenceUri)

Performs relative resolution of a referenceUri with respect to a baseUri.

public static UriresolveToUri(java.lang.String baseUri, java.lang.String referenceUri)

Like UriUtil.resolve(String, String), but returns a instead of a java.lang.String.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static Uri resolveToUri(java.lang.String baseUri, java.lang.String referenceUri)

Like UriUtil.resolve(String, String), but returns a instead of a java.lang.String.

Parameters:

baseUri: The base URI.
referenceUri: The reference URI to resolve.

public static java.lang.String resolve(java.lang.String baseUri, java.lang.String referenceUri)

Performs relative resolution of a referenceUri with respect to a baseUri.

The resolution is performed as specified by RFC-3986.

Parameters:

baseUri: The base URI.
referenceUri: The reference URI to resolve.

public static boolean isAbsolute(java.lang.String uri)

Returns true if the URI is starting with a scheme component, false otherwise.

public static Uri removeQueryParameter(Uri uri, java.lang.String queryParameterName)

Removes query parameter from a URI, if present.

Parameters:

uri: The URI.
queryParameterName: The name of the query parameter.

Returns:

The URI without the query parameter.

public static java.lang.String getRelativePath(Uri baseUri, Uri targetUri)

Calculates the relative path from a base URI to a target URI.

Returns:

The relative path from the base URI to the target URI, or targetUri if the URIs have different schemes or authorities.

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed 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 androidx.media3.common.util;

import static java.lang.Math.min;

import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.common.base.Ascii;
import java.util.List;

/** Utility methods for manipulating URIs. */
@UnstableApi
public final class UriUtil {

  /** The length of arrays returned by {@link #getUriIndices(String)}. */
  private static final int INDEX_COUNT = 4;

  /**
   * An index into an array returned by {@link #getUriIndices(String)}.
   *
   * <p>The value at this position in the array is the index of the ':' after the scheme. Equals -1
   * if the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1),
   * including when the URI has no scheme.
   */
  private static final int SCHEME_COLON = 0;

  /**
   * An index into an array returned by {@link #getUriIndices(String)}.
   *
   * <p>The value at this position in the array is the index of the path part. Equals (schemeColon +
   * 1) if no authority part, (schemeColon + 3) if the authority part consists of just "//", and
   * (query) if no path part. The characters starting at this index can be "//" only if the
   * authority part is non-empty (in this case the double-slash means the first segment is empty).
   */
  private static final int PATH = 1;

  /**
   * An index into an array returned by {@link #getUriIndices(String)}.
   *
   * <p>The value at this position in the array is the index of the query part, including the '?'
   * before the query. Equals fragment if no query part, and (fragment - 1) if the query part is a
   * single '?' with no data.
   */
  private static final int QUERY = 2;

  /**
   * An index into an array returned by {@link #getUriIndices(String)}.
   *
   * <p>The value at this position in the array is the index of the fragment part, including the '#'
   * before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if
   * the fragment part is a single '#' with no data.
   */
  private static final int FRAGMENT = 3;

  private UriUtil() {}

  /**
   * Like {@link #resolve(String, String)}, but returns a {@link Uri} instead of a {@link String}.
   *
   * @param baseUri The base URI.
   * @param referenceUri The reference URI to resolve.
   */
  public static Uri resolveToUri(@Nullable String baseUri, @Nullable String referenceUri) {
    return Uri.parse(resolve(baseUri, referenceUri));
  }

  /**
   * Performs relative resolution of a {@code referenceUri} with respect to a {@code baseUri}.
   *
   * <p>The resolution is performed as specified by RFC-3986.
   *
   * @param baseUri The base URI.
   * @param referenceUri The reference URI to resolve.
   */
  public static String resolve(@Nullable String baseUri, @Nullable String referenceUri) {
    StringBuilder uri = new StringBuilder();

    // Map null onto empty string, to make the following logic simpler.
    baseUri = baseUri == null ? "" : baseUri;
    referenceUri = referenceUri == null ? "" : referenceUri;

    int[] refIndices = getUriIndices(referenceUri);
    if (refIndices[SCHEME_COLON] != -1) {
      // The reference is absolute. The target Uri is the reference.
      uri.append(referenceUri);
      removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]);
      return uri.toString();
    }

    int[] baseIndices = getUriIndices(baseUri);
    if (refIndices[FRAGMENT] == 0) {
      // The reference is empty or contains just the fragment part, then the target Uri is the
      // concatenation of the base Uri without its fragment, and the reference.
      return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString();
    }

    if (refIndices[QUERY] == 0) {
      // The reference starts with the query part. The target is the base up to (but excluding) the
      // query, plus the reference.
      return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString();
    }

    if (refIndices[PATH] != 0) {
      // The reference has authority. The target is the base scheme plus the reference.
      int baseLimit = baseIndices[SCHEME_COLON] + 1;
      uri.append(baseUri, 0, baseLimit).append(referenceUri);
      return removeDotSegments(uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY]);
    }

    if (referenceUri.charAt(refIndices[PATH]) == '/') {
      // The reference path is rooted. The target is the base scheme and authority (if any), plus
      // the reference.
      uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri);
      return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY]);
    }

    // The target Uri is the concatenation of the base Uri up to (but excluding) the last segment,
    // and the reference. This can be split into 2 cases:
    if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH]
        && baseIndices[PATH] == baseIndices[QUERY]) {
      // Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is
      // needed after the authority, before appending the reference.
      uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri);
      return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1);
    } else {
      // Case 2: Otherwise, find the last '/' in the base hier-part and append the reference after
      // it. If base hier-part has no '/', it could only mean that it is completely empty or
      // contains only one segment, in which case the whole hier-part is excluded and the reference
      // is appended right after the base scheme colon without an added '/'.
      int lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1);
      int baseLimit = lastSlashIndex == -1 ? baseIndices[PATH] : lastSlashIndex + 1;
      uri.append(baseUri, 0, baseLimit).append(referenceUri);
      return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]);
    }
  }

  /** Returns true if the URI is starting with a scheme component, false otherwise. */
  public static boolean isAbsolute(@Nullable String uri) {
    return uri != null && getUriIndices(uri)[SCHEME_COLON] != -1;
  }

  /**
   * Removes query parameter from a URI, if present.
   *
   * @param uri The URI.
   * @param queryParameterName The name of the query parameter.
   * @return The URI without the query parameter.
   */
  public static Uri removeQueryParameter(Uri uri, String queryParameterName) {
    Uri.Builder builder = uri.buildUpon();
    builder.clearQuery();
    for (String key : uri.getQueryParameterNames()) {
      if (!key.equals(queryParameterName)) {
        for (String value : uri.getQueryParameters(key)) {
          builder.appendQueryParameter(key, value);
        }
      }
    }
    return builder.build();
  }

  /**
   * Removes dot segments from the path of a URI.
   *
   * @param uri A {@link StringBuilder} containing the URI.
   * @param offset The index of the start of the path in {@code uri}.
   * @param limit The limit (exclusive) of the path in {@code uri}.
   */
  private static String removeDotSegments(StringBuilder uri, int offset, int limit) {
    if (offset >= limit) {
      // Nothing to do.
      return uri.toString();
    }
    if (uri.charAt(offset) == '/') {
      // If the path starts with a /, always retain it.
      offset++;
    }
    // The first character of the current path segment.
    int segmentStart = offset;
    int i = offset;
    while (i <= limit) {
      int nextSegmentStart;
      if (i == limit) {
        nextSegmentStart = i;
      } else if (uri.charAt(i) == '/') {
        nextSegmentStart = i + 1;
      } else {
        i++;
        continue;
      }
      // We've encountered the end of a segment or the end of the path. If the final segment was
      // "." or "..", remove the appropriate segments of the path.
      if (i == segmentStart + 1 && uri.charAt(segmentStart) == '.') {
        // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi".
        uri.delete(segmentStart, nextSegmentStart);
        limit -= nextSegmentStart - segmentStart;
        i = segmentStart;
      } else if (i == segmentStart + 2
          && uri.charAt(segmentStart) == '.'
          && uri.charAt(segmentStart + 1) == '.') {
        // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi".
        int prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1;
        int removeFrom = prevSegmentStart > offset ? prevSegmentStart : offset;
        uri.delete(removeFrom, nextSegmentStart);
        limit -= nextSegmentStart - removeFrom;
        segmentStart = prevSegmentStart;
        i = prevSegmentStart;
      } else {
        i++;
        segmentStart = i;
      }
    }
    return uri.toString();
  }

  /**
   * Calculates indices of the constituent components of a URI.
   *
   * @param uriString The URI as a string.
   * @return The corresponding indices.
   */
  private static int[] getUriIndices(String uriString) {
    int[] indices = new int[INDEX_COUNT];
    if (TextUtils.isEmpty(uriString)) {
      indices[SCHEME_COLON] = -1;
      return indices;
    }

    // Determine outer structure from right to left.
    // Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
    int length = uriString.length();
    int fragmentIndex = uriString.indexOf('#');
    if (fragmentIndex == -1) {
      fragmentIndex = length;
    }
    int queryIndex = uriString.indexOf('?');
    if (queryIndex == -1 || queryIndex > fragmentIndex) {
      // '#' before '?': '?' is within the fragment.
      queryIndex = fragmentIndex;
    }
    // Slashes are allowed only in hier-part so any colon after the first slash is part of the
    // hier-part, not the scheme colon separator.
    int schemeIndexLimit = uriString.indexOf('/');
    if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) {
      schemeIndexLimit = queryIndex;
    }
    int schemeIndex = uriString.indexOf(':');
    if (schemeIndex > schemeIndexLimit) {
      // '/' before ':'
      schemeIndex = -1;
    }

    // Determine hier-part structure: hier-part = "//" authority path / path
    // This block can also cope with schemeIndex == -1.
    boolean hasAuthority =
        schemeIndex + 2 < queryIndex
            && uriString.charAt(schemeIndex + 1) == '/'
            && uriString.charAt(schemeIndex + 2) == '/';
    int pathIndex;
    if (hasAuthority) {
      pathIndex = uriString.indexOf('/', schemeIndex + 3); // find first '/' after "://"
      if (pathIndex == -1 || pathIndex > queryIndex) {
        pathIndex = queryIndex;
      }
    } else {
      pathIndex = schemeIndex + 1;
    }

    indices[SCHEME_COLON] = schemeIndex;
    indices[PATH] = pathIndex;
    indices[QUERY] = queryIndex;
    indices[FRAGMENT] = fragmentIndex;
    return indices;
  }

  /**
   * Calculates the relative path from a base URI to a target URI.
   *
   * @return The relative path from the base URI to the target URI, or {@code targetUri} if the URIs
   *     have different schemes or authorities.
   */
  @UnstableApi
  public static String getRelativePath(Uri baseUri, Uri targetUri) {
    if (baseUri.isOpaque() || targetUri.isOpaque()) {
      return targetUri.toString();
    }

    String baseUriScheme = baseUri.getScheme();
    String targetUriScheme = targetUri.getScheme();
    boolean isSameScheme =
        baseUriScheme == null
            ? targetUriScheme == null
            : targetUriScheme != null && Ascii.equalsIgnoreCase(baseUriScheme, targetUriScheme);
    if (!isSameScheme || !Util.areEqual(baseUri.getAuthority(), targetUri.getAuthority())) {
      // Different schemes or authorities, cannot find relative path, return targetUri.
      return targetUri.toString();
    }

    List<String> basePathSegments = baseUri.getPathSegments();
    List<String> targetPathSegments = targetUri.getPathSegments();

    int commonPrefixCount = 0;
    int minSize = min(basePathSegments.size(), targetPathSegments.size());

    for (int i = 0; i < minSize; i++) {
      if (!basePathSegments.get(i).equals(targetPathSegments.get(i))) {
        break;
      }
      commonPrefixCount++;
    }

    StringBuilder relativePath = new StringBuilder();
    for (int i = commonPrefixCount; i < basePathSegments.size(); i++) {
      relativePath.append("../");
    }

    for (int i = commonPrefixCount; i < targetPathSegments.size(); i++) {
      relativePath.append(targetPathSegments.get(i));
      if (i < targetPathSegments.size() - 1) {
        relativePath.append("/");
      }
    }

    return relativePath.toString();
  }
}