/*
 * Copyright 2002-2018 the original author or authors.
 *
 * 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 org.springframework.web.util;

import java.io.Serializable;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;

Represents an immutable collection of URI components, mapping component type to String values. Contains convenience getters for all components. Effectively similar to URI, but with more powerful encoding options and support for URI template variables.
Author:Arjen Poutsma, Juergen Hoeller, Rossen Stoyanchev
See Also:
Since:3.1
/** * Represents an immutable collection of URI components, mapping component type to * String values. Contains convenience getters for all components. Effectively similar * to {@link java.net.URI}, but with more powerful encoding options and support for * URI template variables. * * @author Arjen Poutsma * @author Juergen Hoeller * @author Rossen Stoyanchev * @since 3.1 * @see UriComponentsBuilder */
@SuppressWarnings("serial") public abstract class UriComponents implements Serializable {
Captures URI template variable names.
/** Captures URI template variable names. */
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); @Nullable private final String scheme; @Nullable private final String fragment; protected UriComponents(@Nullable String scheme, @Nullable String fragment) { this.scheme = scheme; this.fragment = fragment; } // Component getters
Return the scheme. Can be null.
/** * Return the scheme. Can be {@code null}. */
@Nullable public final String getScheme() { return this.scheme; }
Return the fragment. Can be null.
/** * Return the fragment. Can be {@code null}. */
@Nullable public final String getFragment() { return this.fragment; }
Return the scheme specific part. Can be null.
/** * Return the scheme specific part. Can be {@code null}. */
@Nullable public abstract String getSchemeSpecificPart();
Return the user info. Can be null.
/** * Return the user info. Can be {@code null}. */
@Nullable public abstract String getUserInfo();
Return the host. Can be null.
/** * Return the host. Can be {@code null}. */
@Nullable public abstract String getHost();
Return the port. -1 if no port has been set.
/** * Return the port. {@code -1} if no port has been set. */
public abstract int getPort();
Return the path. Can be null.
/** * Return the path. Can be {@code null}. */
@Nullable public abstract String getPath();
Return the list of path segments. Empty if no path has been set.
/** * Return the list of path segments. Empty if no path has been set. */
public abstract List<String> getPathSegments();
Return the query. Can be null.
/** * Return the query. Can be {@code null}. */
@Nullable public abstract String getQuery();
Return the map of query parameters. Empty if no query has been set.
/** * Return the map of query parameters. Empty if no query has been set. */
public abstract MultiValueMap<String, String> getQueryParams();
Invoke this after expanding URI variables to encode the resulting URI component values.

In comparison to UriComponentsBuilder.encode(), this method only replaces non-ASCII and illegal (within a given URI component type) characters, but not characters with reserved meaning. For most cases, UriComponentsBuilder.encode() is more likely to give the expected result.

See Also:
/** * Invoke this <em>after</em> expanding URI variables to encode the * resulting URI component values. * <p>In comparison to {@link UriComponentsBuilder#encode()}, this method * <em>only</em> replaces non-ASCII and illegal (within a given URI * component type) characters, but not characters with reserved meaning. * For most cases, {@link UriComponentsBuilder#encode()} is more likely * to give the expected result. * @see UriComponentsBuilder#encode() */
public final UriComponents encode() { return encode(StandardCharsets.UTF_8); }
A variant of encode() with a charset other than "UTF-8".
Params:
  • charset – the charset to use for encoding
See Also:
/** * A variant of {@link #encode()} with a charset other than "UTF-8". * @param charset the charset to use for encoding * @see UriComponentsBuilder#encode(Charset) */
public abstract UriComponents encode(Charset charset);
Replace all URI template variables with the values from a given map.

The given map keys represent variable names; the corresponding values represent variable values. The order of variables is not significant.

Params:
  • uriVariables – the map of URI variables
Returns:the expanded URI components
/** * Replace all URI template variables with the values from a given map. * <p>The given map keys represent variable names; the corresponding values * represent variable values. The order of variables is not significant. * @param uriVariables the map of URI variables * @return the expanded URI components */
public final UriComponents expand(Map<String, ?> uriVariables) { Assert.notNull(uriVariables, "'uriVariables' must not be null"); return expandInternal(new MapTemplateVariables(uriVariables)); }
Replace all URI template variables with the values from a given array.

The given array represents variable values. The order of variables is significant.

Params:
  • uriVariableValues – the URI variable values
Returns:the expanded URI components
/** * Replace all URI template variables with the values from a given array. * <p>The given array represents variable values. The order of variables is significant. * @param uriVariableValues the URI variable values * @return the expanded URI components */
public final UriComponents expand(Object... uriVariableValues) { Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null"); return expandInternal(new VarArgsTemplateVariables(uriVariableValues)); }
Replace all URI template variables with the values from the given UriTemplateVariables.
Params:
  • uriVariables – the URI template values
Returns:the expanded URI components
/** * Replace all URI template variables with the values from the given * {@link UriTemplateVariables}. * @param uriVariables the URI template values * @return the expanded URI components */
public final UriComponents expand(UriTemplateVariables uriVariables) { Assert.notNull(uriVariables, "'uriVariables' must not be null"); return expandInternal(uriVariables); }
Replace all URI template variables with the values from the given UriTemplateVariables.
Params:
  • uriVariables – the URI template values
Returns:the expanded URI components
/** * Replace all URI template variables with the values from the given {@link * UriTemplateVariables}. * @param uriVariables the URI template values * @return the expanded URI components */
abstract UriComponents expandInternal(UriTemplateVariables uriVariables);
Normalize the path removing sequences like "path/..". Note that normalization is applied to the full path, and not to individual path segments.
See Also:
  • cleanPath.cleanPath(String)
/** * Normalize the path removing sequences like "path/..". Note that * normalization is applied to the full path, and not to individual path * segments. * @see org.springframework.util.StringUtils#cleanPath(String) */
public abstract UriComponents normalize();
Concatenate all URI components to return the fully formed URI String.

This method does nothing more than a simple concatenation based on current values. That means it could produce different results if invoked before vs after methods that can change individual values such as encode, expand, or normalize.

/** * Concatenate all URI components to return the fully formed URI String. * <p>This method does nothing more than a simple concatenation based on * current values. That means it could produce different results if invoked * before vs after methods that can change individual values such as * {@code encode}, {@code expand}, or {@code normalize}. */
public abstract String toUriString();
Create a URI from this instance as follows:

If the current instance is encoded, form the full URI String via toUriString(), and then pass it to the single argument URI constructor which preserves percent encoding.

If not yet encoded, pass individual URI component values to the multi-argument URI constructor which quotes illegal characters that cannot appear in their respective URI component.

/** * Create a {@link URI} from this instance as follows: * <p>If the current instance is {@link #encode() encoded}, form the full * URI String via {@link #toUriString()}, and then pass it to the single * argument {@link URI} constructor which preserves percent encoding. * <p>If not yet encoded, pass individual URI component values to the * multi-argument {@link URI} constructor which quotes illegal characters * that cannot appear in their respective URI component. */
public abstract URI toUri();
A simple pass-through to toUriString().
/** * A simple pass-through to {@link #toUriString()}. */
@Override public final String toString() { return toUriString(); }
Set all components of the given UriComponentsBuilder.
Since:4.2
/** * Set all components of the given UriComponentsBuilder. * @since 4.2 */
protected abstract void copyToUriComponentsBuilder(UriComponentsBuilder builder); // Static expansion helpers @Nullable static String expandUriComponent(@Nullable String source, UriTemplateVariables uriVariables) { return expandUriComponent(source, uriVariables, null); } @Nullable static String expandUriComponent(@Nullable String source, UriTemplateVariables uriVariables, @Nullable UnaryOperator<String> encoder) { if (source == null) { return null; } if (source.indexOf('{') == -1) { return source; } if (source.indexOf(':') != -1) { source = sanitizeSource(source); } Matcher matcher = NAMES_PATTERN.matcher(source); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String match = matcher.group(1); String varName = getVariableName(match); Object varValue = uriVariables.getValue(varName); if (UriTemplateVariables.SKIP_VALUE.equals(varValue)) { continue; } String formatted = getVariableValueAsString(varValue); formatted = encoder != null ? encoder.apply(formatted) : Matcher.quoteReplacement(formatted); matcher.appendReplacement(sb, formatted); } matcher.appendTail(sb); return sb.toString(); }
Remove nested "{}" such as in URI vars with regular expressions.
/** * Remove nested "{}" such as in URI vars with regular expressions. */
private static String sanitizeSource(String source) { int level = 0; StringBuilder sb = new StringBuilder(); for (char c : source.toCharArray()) { if (c == '{') { level++; } if (c == '}') { level--; } if (level > 1 || (level == 1 && c == '}')) { continue; } sb.append(c); } return sb.toString(); } private static String getVariableName(String match) { int colonIdx = match.indexOf(':'); return (colonIdx != -1 ? match.substring(0, colonIdx) : match); } private static String getVariableValueAsString(@Nullable Object variableValue) { return (variableValue != null ? variableValue.toString() : ""); }
Defines the contract for URI Template variables.
See Also:
  • expand.expand
/** * Defines the contract for URI Template variables. * @see HierarchicalUriComponents#expand */
public interface UriTemplateVariables {
Constant for a value that indicates a URI variable name should be ignored and left as is. This is useful for partial expanding of some but not all URI variables.
/** * Constant for a value that indicates a URI variable name should be * ignored and left as is. This is useful for partial expanding of some * but not all URI variables. */
Object SKIP_VALUE = UriTemplateVariables.class;
Get the value for the given URI variable name. If the value is null, an empty String is expanded. If the value is SKIP_VALUE, the URI variable is not expanded.
Params:
  • name – the variable name
Returns:the variable value, possibly null or SKIP_VALUE
/** * Get the value for the given URI variable name. * If the value is {@code null}, an empty String is expanded. * If the value is {@link #SKIP_VALUE}, the URI variable is not expanded. * @param name the variable name * @return the variable value, possibly {@code null} or {@link #SKIP_VALUE} */
@Nullable Object getValue(@Nullable String name); }
URI template variables backed by a map.
/** * URI template variables backed by a map. */
private static class MapTemplateVariables implements UriTemplateVariables { private final Map<String, ?> uriVariables; public MapTemplateVariables(Map<String, ?> uriVariables) { this.uriVariables = uriVariables; } @Override @Nullable public Object getValue(@Nullable String name) { if (!this.uriVariables.containsKey(name)) { throw new IllegalArgumentException("Map has no value for '" + name + "'"); } return this.uriVariables.get(name); } }
URI template variables backed by a variable argument array.
/** * URI template variables backed by a variable argument array. */
private static class VarArgsTemplateVariables implements UriTemplateVariables { private final Iterator<Object> valueIterator; public VarArgsTemplateVariables(Object... uriVariableValues) { this.valueIterator = Arrays.asList(uriVariableValues).iterator(); } @Override @Nullable public Object getValue(@Nullable String name) { if (!this.valueIterator.hasNext()) { throw new IllegalArgumentException("Not enough variable values available to expand '" + name + "'"); } return this.valueIterator.next(); } } }