All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.core.util.UrlBuilder Maven / Gradle / Ivy

There is a newer version: 1.54.1
Show newest version
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.util;

import com.azure.core.implementation.ImplUtils;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static com.azure.core.implementation.ImplUtils.MAX_CACHE_SIZE;

/**
 * A builder class that is used to create URLs.
 */
public final class UrlBuilder {
    private static final Map PARSED_URLS = new ConcurrentHashMap<>();

    private String scheme;
    private String host;
    private Integer port;
    private String path;

    private Map queryToCopy;
    private Map query;

    /**
     * Creates a new instance of {@link UrlBuilder}.
     */
    public UrlBuilder() {
        this(null);
    }

    private UrlBuilder(Map queryToCopy) {
        this.queryToCopy = queryToCopy;
    }

    /**
     * Set the scheme/protocol that will be used to build the final URL.
     *
     * @param scheme The scheme/protocol that will be used to build the final URL.
     * @return This UrlBuilder so that multiple setters can be chained together.
     */
    public UrlBuilder setScheme(String scheme) {
        if (scheme == null || scheme.isEmpty()) {
            this.scheme = null;
        } else {
            with(scheme, UrlTokenizerState.SCHEME);
        }
        return this;
    }

    /**
     * Get the scheme/protocol that has been assigned to this UrlBuilder.
     *
     * @return the scheme/protocol that has been assigned to this UrlBuilder.
     */
    public String getScheme() {
        return scheme;
    }

    /**
     * Set the host that will be used to build the final URL.
     *
     * @param host The host that will be used to build the final URL.
     * @return This UrlBuilder so that multiple setters can be chained together.
     */
    public UrlBuilder setHost(String host) {
        if (host == null || host.isEmpty()) {
            this.host = null;
        } else {
            with(host, UrlTokenizerState.SCHEME_OR_HOST);
        }
        return this;
    }

    /**
     * Get the host that has been assigned to this UrlBuilder.
     *
     * @return the host that has been assigned to this UrlBuilder.
     */
    public String getHost() {
        return host;
    }

    /**
     * Set the port that will be used to build the final URL.
     *
     * @param port The port that will be used to build the final URL.
     * @return This UrlBuilder so that multiple setters can be chained together.
     */
    public UrlBuilder setPort(String port) {
        if (CoreUtils.isNullOrEmpty(port)) {
            this.port = null;
            return this;
        }

        return with(port, UrlTokenizerState.PORT);
    }

    /**
     * Set the port that will be used to build the final URL.
     *
     * @param port The port that will be used to build the final URL.
     * @return This UrlBuilder so that multiple setters can be chained together.
     */
    public UrlBuilder setPort(int port) {
        this.port = port;
        return this;
    }

    /**
     * Get the port that has been assigned to this UrlBuilder.
     *
     * @return the port that has been assigned to this UrlBuilder.
     */
    public Integer getPort() {
        return port;
    }

    /**
     * Set the path that will be used to build the final URL.
     *
     * @param path The path that will be used to build the final URL.
     * @return This UrlBuilder so that multiple setters can be chained together.
     */
    public UrlBuilder setPath(String path) {
        if (path == null || path.isEmpty()) {
            this.path = null;
        } else {
            with(path, UrlTokenizerState.PATH);
        }
        return this;
    }

    /**
     * Get the path that has been assigned to this UrlBuilder.
     *
     * @return the path that has been assigned to this UrlBuilder.
     */
    public String getPath() {
        return path;
    }

    /**
     * Set the provided query parameter name and encoded value to query string for the final URL.
     *
     * @param queryParameterName The name of the query parameter.
     * @param queryParameterEncodedValue The encoded value of the query parameter.
     * @return The provided query parameter name and encoded value to query string for the final URL.
     * @throws NullPointerException if {@code queryParameterName} or {@code queryParameterEncodedValue} are null.
     */
    public UrlBuilder setQueryParameter(String queryParameterName, String queryParameterEncodedValue) {
        initializeQuery();

        query.put(queryParameterName, new QueryParameter(queryParameterName, queryParameterEncodedValue));
        return this;
    }

    /**
     * Append the provided query parameter name and encoded value to query string for the final URL.
     *
     * @param queryParameterName The name of the query parameter.
     * @param queryParameterEncodedValue The encoded value of the query parameter.
     * @return The provided query parameter name and encoded value to query string for the final URL.
     * @throws NullPointerException if {@code queryParameterName} or {@code queryParameterEncodedValue} are null.
     */
    public UrlBuilder addQueryParameter(String queryParameterName, String queryParameterEncodedValue) {
        initializeQuery();

        query.compute(queryParameterName, (key, value) -> {
            if (value == null) {
                return new QueryParameter(queryParameterName, queryParameterEncodedValue);
            }
            value.addValue(queryParameterEncodedValue);
            return value;
        });
        return this;
    }

    /**
     * Set the query that will be used to build the final URL.
     *
     * @param query The query that will be used to build the final URL.
     * @return This UrlBuilder so that multiple setters can be chained together.
     */
    public UrlBuilder setQuery(String query) {
        return (query == null || query.isEmpty()) ? clearQuery() : with(query, UrlTokenizerState.QUERY);
    }

    /**
     * Clear the query that will be used to build the final URL.
     *
     * @return This UrlBuilder so that multiple setters can be chained together.
     */
    public UrlBuilder clearQuery() {
        if (CoreUtils.isNullOrEmpty(query)) {
            return this;
        }

        query.clear();
        return this;
    }

    /**
     * Get a view of the query that has been assigned to this UrlBuilder.
     * 

* Changes to the {@link Map} returned by this API won't be reflected in the UrlBuilder. * * @return A view of the query that has been assigned to this UrlBuilder. */ public Map getQuery() { initializeQuery(); // This contains a map of key=value query parameters, replacing // multiple values for a single key with a list of values under the same name, // joined together with a comma. As discussed in https://github.com/Azure/azure-sdk-for-java/pull/21203. return query.entrySet().stream() // get all parameters joined by a comma. // name=a&name=b&name=c becomes name=a,b,c .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getValue())); } /** * Returns the query string currently configured in this UrlBuilder instance. * * @return A String containing the currently configured query string. */ public String getQueryString() { if (CoreUtils.isNullOrEmpty(queryToCopy) && CoreUtils.isNullOrEmpty(query)) { return ""; } StringBuilder queryBuilder = new StringBuilder(); appendQueryString(queryBuilder); return queryBuilder.toString(); } private void appendQueryString(StringBuilder stringBuilder) { if (CoreUtils.isNullOrEmpty(queryToCopy) && CoreUtils.isNullOrEmpty(query)) { return; } stringBuilder.append('?'); boolean first = true; // queryToCopy hasn't been copied yet as no operations on query parameters have been applied since creating // this UrlBuilder. Use queryToCopy to create the query string and while doing so copy it into query. if (query == null) { query = new LinkedHashMap<>(queryToCopy.size()); for (Map.Entry entry : queryToCopy.entrySet()) { first = writeQueryValues(stringBuilder, entry.getKey(), entry.getValue().getValuesList(), first); query.put(entry.getKey(), entry.getValue()); } } else { // queryToCopy has been copied, use query to build the query string. for (Map.Entry entry : query.entrySet()) { first = writeQueryValues(stringBuilder, entry.getKey(), entry.getValue().getValuesList(), first); } } } private static boolean writeQueryValues(StringBuilder builder, String key, List values, boolean first) { for (String value : values) { if (!first) { builder.append('&'); } builder.append(key).append('=').append(value); first = false; } return first; } private UrlBuilder with(String text, UrlTokenizerState startState) { final UrlTokenizer tokenizer = new UrlTokenizer(text, startState); while (tokenizer.next()) { final UrlToken token = tokenizer.current(); final String tokenText = emptyToNull(token.text()); final UrlTokenType tokenType = token.type(); switch (tokenType) { case SCHEME: scheme = tokenText; break; case HOST: host = tokenText; break; case PORT: port = tokenText == null ? null : Integer.parseInt(tokenText); break; case PATH: if (path == null || "/".equals(path) || !"/".equals(tokenText)) { path = tokenText; } break; case QUERY: CoreUtils.parseQueryParameters(tokenText).forEachRemaining(queryParam -> addQueryParameter(queryParam.getKey(), queryParam.getValue())); break; default: break; } } return this; } /** * Get the URL that is being built. * * @return The URL that is being built. * @throws MalformedURLException if the URL is not fully formed. */ public URL toUrl() throws MalformedURLException { // Continue using new URL constructor here as URI either cannot accept certain characters in the path or // escapes '/', depending on the API used to create the URI. return ImplUtils.createUrl(toString()); } /** * Get the string representation of the URL that is being built. * * @return The string representation of the URL that is being built. */ @Override public String toString() { final StringBuilder result = new StringBuilder(); final boolean isAbsolutePath = path != null && (path.startsWith("http://") || path.startsWith("https://")); if (!isAbsolutePath) { if (scheme != null) { result.append(scheme); if (!scheme.endsWith("://")) { result.append("://"); } } if (host != null) { result.append(host); } } if (port != null) { result.append(':'); result.append(port); } if (path != null) { if (result.length() != 0 && !path.startsWith("/")) { result.append('/'); } result.append(path); } appendQueryString(result); return result.toString(); } /** * Returns the map of parsed URLs and their {@link UrlBuilder UrlBuilders} * * @return the map of parsed URLs and their {@link UrlBuilder UrlBuilders} */ static Map getParsedUrls() { return PARSED_URLS; } /** * Parses the passed {@code url} string into a UrlBuilder. * * @param url The URL string to parse. * @return The UrlBuilder that was created from parsing the passed URL string. */ public static UrlBuilder parse(String url) { /* * Parsing the URL string into a UrlBuilder is a non-trivial operation and many calls into RestProxy will use * the same root URL string. To save CPU costs we retain a parsed version of the URL string in memory. Given * that UrlBuilder is mutable we must return a cloned version of the cached UrlBuilder. */ // ConcurrentHashMap doesn't allow for null keys, coerce it into an empty string. String concurrentSafeUrl = (url == null) ? "" : url; // If the number of parsed urls are above threshold, clear the map and start fresh. // This prevents the map from growing without bounds if too many unique URLs are parsed. // TODO (srnagar): consider using an LRU cache to evict selectively if (PARSED_URLS.size() >= MAX_CACHE_SIZE) { PARSED_URLS.clear(); } return PARSED_URLS.computeIfAbsent(concurrentSafeUrl, u -> new UrlBuilder().with(u, UrlTokenizerState.SCHEME_OR_HOST)).copy(); } /** * Parse a UrlBuilder from the provided URL object. * * @param url The URL object to parse. * @return The UrlBuilder that was parsed from the URL object. */ public static UrlBuilder parse(URL url) { return ImplUtils.parseUrl(url, true); } private static String emptyToNull(String value) { return value == null || value.isEmpty() ? null : value; } private UrlBuilder copy() { UrlBuilder copy = new UrlBuilder(query); copy.scheme = this.scheme; copy.host = this.host; copy.path = this.path; copy.port = this.port; return copy; } private void initializeQuery() { if (query == null) { query = new LinkedHashMap<>(); } if (queryToCopy != null) { query.putAll(queryToCopy); queryToCopy = null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy