| /* |
| * Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| package org.glassfish.jersey.uri; |
| |
| import java.net.URI; |
| import java.util.ArrayDeque; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| import org.glassfish.jersey.internal.guava.Preconditions; |
| import org.glassfish.jersey.uri.internal.UriTemplateParser; |
| |
| /** |
| * A URI template. |
| * |
| * @author Paul Sandoz |
| * @author Martin Matula |
| * @author Gerard Davison (gerard.davison at oracle.com) |
| */ |
| public class UriTemplate { |
| private static final String[] EMPTY_VALUES = new String[0]; |
| |
| /** |
| * Order the templates according to JAX-RS specification. |
| * <p> |
| * Sort the set of matching resource classes using the number of |
| * characters in the regular expression not resulting from template |
| * variables as the primary key, the number of matching groups |
| * as a secondary key, and the number of explicit regular expression |
| * declarations as the tertiary key. |
| * </p> |
| */ |
| public static final Comparator<UriTemplate> COMPARATOR = new Comparator<UriTemplate>() { |
| |
| @Override |
| public int compare(UriTemplate o1, UriTemplate o2) { |
| if (o1 == null && o2 == null) { |
| return 0; |
| } |
| if (o1 == null) { |
| return 1; |
| } |
| if (o2 == null) { |
| return -1; |
| } |
| |
| if (o1 == EMPTY && o2 == EMPTY) { |
| return 0; |
| } |
| if (o1 == EMPTY) { |
| return 1; |
| } |
| if (o2 == EMPTY) { |
| return -1; |
| } |
| |
| // Compare the number of explicit characters |
| // Note that it is important that o2 is compared against o1 |
| // so that a regular expression with say 10 explicit characters |
| // is less than a regular expression with say 5 explicit characters. |
| int i = o2.getNumberOfExplicitCharacters() - o1.getNumberOfExplicitCharacters(); |
| if (i != 0) { |
| return i; |
| } |
| |
| // If the number of explicit characters is equal |
| // compare the number of template variables |
| // Note that it is important that o2 is compared against o1 |
| // so that a regular expression with say 10 template variables |
| // is less than a regular expression with say 5 template variables. |
| i = o2.getNumberOfTemplateVariables() - o1.getNumberOfTemplateVariables(); |
| if (i != 0) { |
| return i; |
| } |
| |
| // If the number of template variables is equal |
| // compare the number of explicit regexes |
| i = o2.getNumberOfExplicitRegexes() - o1.getNumberOfExplicitRegexes(); |
| if (i != 0) { |
| return i; |
| } |
| |
| // If the number of explicit characters and template variables |
| // are equal then |
| // the order does not matter as long as templates with different |
| // explicit characters are distinguishable |
| return 0; |
| } |
| }; |
| |
| /** |
| * A strategy interface for processing parameters, should be replaced with |
| * a JDK 8 one day in the future. |
| */ |
| private static interface TemplateValueStrategy { |
| /** |
| * Get a value for a given template variable. |
| * |
| * @param templateVariable template variable. |
| * @param matchedGroup matched group string for a given template variable. |
| * @return template value. |
| * |
| * @throws java.lang.IllegalArgumentException in case no value has been found and the strategy |
| * does not support {@code null} values. |
| */ |
| public String valueFor(String templateVariable, String matchedGroup); |
| } |
| |
| /** |
| * The regular expression for matching URI templates and names. |
| */ |
| private static final Pattern TEMPLATE_NAMES_PATTERN = Pattern.compile("\\{([\\w\\?;][-\\w\\.,]*)\\}"); |
| |
| /** |
| * The empty URI template that matches the {@code null} or empty URI path. |
| */ |
| public static final UriTemplate EMPTY = new UriTemplate(); |
| /** |
| * The URI template. |
| */ |
| private final String template; |
| /** |
| * The normalized URI template. Any explicit regex are removed to leave |
| * the template variables. |
| */ |
| private final String normalizedTemplate; |
| /** |
| * The pattern generated from the template. |
| */ |
| private final PatternWithGroups pattern; |
| /** |
| * True if the URI template ends in a '/' character. |
| */ |
| private final boolean endsWithSlash; |
| /** |
| * The template variables in the URI template. |
| */ |
| private final List<String> templateVariables; |
| /** |
| * The number of explicit regular expressions declared for template |
| * variables. |
| */ |
| private final int numOfExplicitRegexes; |
| |
| /** |
| * The number of regular expression groups in this pattern. |
| */ |
| private final int numOfRegexGroups; |
| |
| /** |
| * The number of characters in the regular expression not resulting |
| * from conversion of template variables. |
| */ |
| private final int numOfCharacters; |
| |
| /** |
| * Constructor for {@code NULL} template. |
| */ |
| private UriTemplate() { |
| this.template = this.normalizedTemplate = ""; |
| this.pattern = PatternWithGroups.EMPTY; |
| this.endsWithSlash = false; |
| this.templateVariables = Collections.emptyList(); |
| this.numOfExplicitRegexes = this.numOfCharacters = this.numOfRegexGroups = 0; |
| } |
| |
| /** |
| * Construct a new URI template. |
| * <p> |
| * The template will be parsed to extract template variables. |
| * </p> |
| * <p> |
| * A specific regular expression will be generated from the template |
| * to match URIs according to the template and map template variables to |
| * template values. |
| * </p> |
| * |
| * @param template the template. |
| * @throws PatternSyntaxException if the specified |
| * regular expression could not be generated |
| * @throws IllegalArgumentException if the template is {@code null} or |
| * an empty string. |
| */ |
| @SuppressWarnings("DuplicateThrows") |
| public UriTemplate(String template) throws PatternSyntaxException, IllegalArgumentException { |
| this(new UriTemplateParser(template)); |
| } |
| |
| /** |
| * Construct a new URI template. |
| * <p> |
| * The template will be parsed to extract template variables. |
| * <p> |
| * A specific regular expression will be generated from the template |
| * to match URIs according to the template and map template variables to |
| * template values. |
| * <p> |
| * |
| * @param templateParser the parser to parse the template. |
| * @throws PatternSyntaxException if the specified |
| * regular expression could not be generated |
| * @throws IllegalArgumentException if the template is {@code null} or |
| * an empty string. |
| */ |
| @SuppressWarnings("DuplicateThrows") |
| protected UriTemplate(UriTemplateParser templateParser) throws PatternSyntaxException, IllegalArgumentException { |
| this.template = templateParser.getTemplate(); |
| |
| this.normalizedTemplate = templateParser.getNormalizedTemplate(); |
| |
| this.pattern = initUriPattern(templateParser); |
| |
| this.numOfExplicitRegexes = templateParser.getNumberOfExplicitRegexes(); |
| |
| this.numOfRegexGroups = templateParser.getNumberOfRegexGroups(); |
| |
| this.numOfCharacters = templateParser.getNumberOfLiteralCharacters(); |
| |
| this.endsWithSlash = template.charAt(template.length() - 1) == '/'; |
| |
| this.templateVariables = Collections.unmodifiableList(templateParser.getNames()); |
| } |
| |
| /** |
| * Create the URI pattern from a URI template parser. |
| * |
| * @param templateParser the URI template parser. |
| * @return the URI pattern. |
| */ |
| private static PatternWithGroups initUriPattern(UriTemplateParser templateParser) { |
| return new PatternWithGroups(templateParser.getPattern(), templateParser.getGroupIndexes()); |
| } |
| |
| /** |
| * Resolve a relative URI reference against a base URI as defined in |
| * <a href="http://tools.ietf.org/html/rfc3986#section-5.4">RFC 3986</a>. |
| * |
| * @param baseUri base URI to be used for resolution. |
| * @param refUri reference URI string to be resolved against the base URI. |
| * @return resolved URI. |
| * |
| * @throws IllegalArgumentException If the given string violates the URI specification RFC. |
| */ |
| public static URI resolve(final URI baseUri, String refUri) { |
| return resolve(baseUri, URI.create(refUri)); |
| } |
| |
| /** |
| * Resolve a relative URI reference against a base URI as defined in |
| * <a href="http://tools.ietf.org/html/rfc3986#section-5.4">RFC 3986</a>. |
| * |
| * @param baseUri base URI to be used for resolution. |
| * @param refUri reference URI to be resolved against the base URI. |
| * @return resolved URI. |
| */ |
| public static URI resolve(final URI baseUri, URI refUri) { |
| Preconditions.checkNotNull(baseUri, "Input base URI parameter must not be null."); |
| Preconditions.checkNotNull(refUri, "Input reference URI parameter must not be null."); |
| |
| final String refString = refUri.toString(); |
| if (refString.isEmpty()) { |
| // we need something to resolve against |
| refUri = URI.create("#"); |
| } else if (refString.startsWith("?")) { |
| String baseString = baseUri.toString(); |
| final int qIndex = baseString.indexOf('?'); |
| baseString = qIndex > -1 ? baseString.substring(0, qIndex) : baseString; |
| return URI.create(baseString + refString); |
| } |
| |
| URI result = baseUri.resolve(refUri); |
| if (refString.isEmpty()) { |
| final String resolvedString = result.toString(); |
| result = URI.create(resolvedString.substring(0, resolvedString.indexOf('#'))); |
| } |
| |
| return normalize(result); |
| } |
| |
| /** |
| * Normalize the URI by resolve the dot & dot-dot path segments as described in |
| * <a href="http://tools.ietf.org/html/rfc3986#section-5.2.4">RFC 3986</a>. |
| * |
| * This method provides a workaround for issues with {@link java.net.URI#normalize()} which |
| * is not able to properly normalize absolute paths that start with a {@code ".."} segment, |
| * e.g. {@code "/../a/b"} as required by RFC 3986 (according to RFC 3986 the path {@code "/../a/b"} |
| * should resolve to {@code "/a/b"}, while {@code URI.normalize()} keeps the {@code ".."} segment |
| * in the URI path. |
| * |
| * @param uri the original URI string. |
| * @return the URI with dot and dot-dot segments resolved. |
| * |
| * @throws IllegalArgumentException If the given string violates the URI specification RFC. |
| * @see java.net.URI#normalize() |
| */ |
| public static URI normalize(final String uri) { |
| return normalize(URI.create(uri)); |
| } |
| |
| /** |
| * Normalize the URI by resolve the dot & dot-dot path segments as described in |
| * <a href="http://tools.ietf.org/html/rfc3986#section-5.2.4">RFC 3986</a>. |
| * |
| * This method provides a workaround for issues with {@link java.net.URI#normalize()} which |
| * is not able to properly normalize absolute paths that start with a {@code ".."} segment, |
| * e.g. {@code "/../a/b"} as required by RFC 3986 (according to RFC 3986 the path {@code "/../a/b"} |
| * should resolve to {@code "/a/b"}, while {@code URI.normalize()} keeps the {@code ".."} segment |
| * in the URI path. |
| * |
| * @param uri the original URI. |
| * @return the URI with dot and dot-dot segments resolved. |
| * |
| * @see java.net.URI#normalize() |
| */ |
| public static URI normalize(final URI uri) { |
| Preconditions.checkNotNull(uri, "Input reference URI parameter must not be null."); |
| |
| final String path = uri.getPath(); |
| |
| if (path == null || path.isEmpty() || !path.contains("/.")) { |
| return uri; |
| } |
| |
| final String[] segments = path.split("/"); |
| final Deque<String> resolvedSegments = new ArrayDeque<String>(segments.length); |
| |
| for (final String segment : segments) { |
| if (segment.isEmpty() || ".".equals(segment)) { |
| // skip |
| } else if ("..".equals(segment)) { |
| resolvedSegments.pollLast(); |
| } else { |
| resolvedSegments.offer(segment); |
| } |
| } |
| |
| final StringBuilder pathBuilder = new StringBuilder(); |
| for (final String segment : resolvedSegments) { |
| pathBuilder.append('/').append(segment); |
| } |
| |
| String resultString = createURIWithStringValues(uri.getScheme(), |
| uri.getAuthority(), |
| null, |
| null, |
| null, |
| pathBuilder.toString(), |
| uri.getQuery(), |
| uri.getFragment(), |
| EMPTY_VALUES, |
| false, |
| false); |
| |
| return URI.create(resultString); |
| } |
| |
| /** |
| * Relativize URI with respect to a base URI. |
| * |
| * After the relativization is done, dots in paths of both URIs are {@link #normalize(java.net.URI) resolved}. |
| * |
| * @param baseUri base URI to be used for relativization. |
| * @param refUri URI to be relativized. |
| * @return relativized URI. |
| */ |
| public static URI relativize(URI baseUri, URI refUri) { |
| Preconditions.checkNotNull(baseUri, "Input base URI parameter must not be null."); |
| Preconditions.checkNotNull(refUri, "Input reference URI parameter must not be null."); |
| |
| return normalize(baseUri.relativize(refUri)); |
| } |
| |
| /** |
| * Get the URI template as a String. |
| * |
| * @return the URI template. |
| */ |
| public final String getTemplate() { |
| return template; |
| } |
| |
| |
| /** |
| * Get the URI pattern. |
| * |
| * @return the URI pattern. |
| */ |
| public final PatternWithGroups getPattern() { |
| return pattern; |
| } |
| |
| /** |
| * Check if the URI template ends in a slash ({@code '/'}). |
| * |
| * @return {@code true} if the template ends in a '/', otherwise false. |
| */ |
| @SuppressWarnings("UnusedDeclaration") |
| public final boolean endsWithSlash() { |
| return endsWithSlash; |
| } |
| |
| /** |
| * Get the list of template variables for the template. |
| * |
| * @return the list of template variables. |
| */ |
| public final List<String> getTemplateVariables() { |
| return templateVariables; |
| } |
| |
| /** |
| * Ascertain if a template variable is a member of this |
| * template. |
| * |
| * @param name name The template variable. |
| * @return {@code true} if the template variable is a member of the template, otherwise {@code false}. |
| */ |
| @SuppressWarnings("UnusedDeclaration") |
| public final boolean isTemplateVariablePresent(String name) { |
| for (String s : templateVariables) { |
| if (s.equals(name)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Get the number of explicit regular expressions declared in the template variables. |
| * |
| * @return the number of explicit regular expressions in the template variables. |
| */ |
| public final int getNumberOfExplicitRegexes() { |
| return numOfExplicitRegexes; |
| } |
| |
| /** |
| * Get the number of regular expression groups |
| * |
| * @return the number of regular expressions groups |
| */ |
| public final int getNumberOfRegexGroups() { |
| return numOfRegexGroups; |
| } |
| |
| /** |
| * Get the number of characters in the regular expression not resulting |
| * from conversion of template variables. |
| * |
| * @return the number of explicit characters |
| */ |
| public final int getNumberOfExplicitCharacters() { |
| return numOfCharacters; |
| } |
| |
| /** |
| * Get the number of template variables. |
| * |
| * @return the number of template variables. |
| */ |
| public final int getNumberOfTemplateVariables() { |
| return templateVariables.size(); |
| } |
| |
| /** |
| * Match a URI against the template. |
| * <p> |
| * If the URI matches against the pattern then the template variable to value |
| * map will be filled with template variables as keys and template values as |
| * values. |
| * <p> |
| * |
| * @param uri the uri to match against the template. |
| * @param templateVariableToValue the map where to put template variables (as keys) |
| * and template values (as values). The map is cleared before any |
| * entries are put. |
| * @return true if the URI matches the template, otherwise false. |
| * |
| * @throws IllegalArgumentException if the uri or |
| * templateVariableToValue is null. |
| */ |
| public final boolean match(CharSequence uri, Map<String, String> templateVariableToValue) throws |
| IllegalArgumentException { |
| if (templateVariableToValue == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| return pattern.match(uri, templateVariables, templateVariableToValue); |
| } |
| |
| /** |
| * Match a URI against the template. |
| * <p> |
| * If the URI matches against the pattern the capturing group values (if any) |
| * will be added to a list passed in as parameter. |
| * <p> |
| * |
| * @param uri the uri to match against the template. |
| * @param groupValues the list to store the values of a pattern's |
| * capturing groups is matching is successful. The values are stored |
| * in the same order as the pattern's capturing groups. |
| * @return true if the URI matches the template, otherwise false. |
| * |
| * @throws IllegalArgumentException if the uri or |
| * templateVariableToValue is null. |
| */ |
| public final boolean match(CharSequence uri, List<String> groupValues) throws |
| IllegalArgumentException { |
| if (groupValues == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| return pattern.match(uri, groupValues); |
| } |
| |
| /** |
| * Create a URI by substituting any template variables |
| * for corresponding template values. |
| * <p> |
| * A URI template variable without a value will be substituted by the |
| * empty string. |
| * |
| * @param values the map of template variables to template values. |
| * @return the URI. |
| */ |
| public final String createURI(final Map<String, String> values) { |
| final StringBuilder sb = new StringBuilder(); |
| resolveTemplate(normalizedTemplate, sb, new TemplateValueStrategy() { |
| @Override |
| public String valueFor(String templateVariable, String matchedGroup) { |
| return values.get(templateVariable); |
| } |
| }); |
| return sb.toString(); |
| } |
| |
| /** |
| * Create a URI by substituting any template variables |
| * for corresponding template values. |
| * <p> |
| * A URI template variable without a value will be substituted by the |
| * empty string. |
| * |
| * @param values the array of template values. The values will be |
| * substituted in order of occurrence of unique template variables. |
| * @return the URI. |
| */ |
| public final String createURI(String... values) { |
| return createURI(values, 0, values.length); |
| } |
| |
| /** |
| * Create a URI by substituting any template variables |
| * for corresponding template values. |
| * <p> |
| * A URI template variable without a value will be substituted by the |
| * empty string. |
| * |
| * @param values the array of template values. The values will be |
| * substituted in order of occurrence of unique template variables. |
| * @param offset the offset into the template value array. |
| * @param length the length of the template value array. |
| * @return the URI. |
| */ |
| public final String createURI(final String[] values, final int offset, final int length) { |
| |
| TemplateValueStrategy ns = new TemplateValueStrategy() { |
| private final int lengthPlusOffset = length + offset; |
| private int v = offset; |
| private final Map<String, String> mapValues = new HashMap<String, String>(); |
| |
| @Override |
| public String valueFor(String templateVariable, String matchedGroup) { |
| // Check if a template variable has already occurred |
| // If so use the value to ensure that two or more declarations of |
| // a template variable have the same value |
| String tValue = mapValues.get(templateVariable); |
| if (tValue == null) { |
| if (v < lengthPlusOffset) { |
| tValue = values[v++]; |
| if (tValue != null) { |
| mapValues.put(templateVariable, tValue); |
| } |
| } |
| } |
| |
| return tValue; |
| } |
| }; |
| |
| final StringBuilder sb = new StringBuilder(); |
| resolveTemplate(normalizedTemplate, sb, ns); |
| return sb.toString(); |
| } |
| |
| /** |
| * Build a URI based on the parameters provided by the variable name strategy. |
| * |
| * @param normalizedTemplate normalized URI template. A normalized template is a template without any explicit regular |
| * expressions. |
| * @param builder URI string builder to be used. |
| * @param valueStrategy The template value producer strategy to use. |
| */ |
| private static void resolveTemplate( |
| String normalizedTemplate, |
| StringBuilder builder, |
| TemplateValueStrategy valueStrategy) { |
| // Find all template variables |
| Matcher m = TEMPLATE_NAMES_PATTERN.matcher(normalizedTemplate); |
| |
| int i = 0; |
| while (m.find()) { |
| builder.append(normalizedTemplate, i, m.start()); |
| String variableName = m.group(1); |
| // TODO matrix |
| char firstChar = variableName.charAt(0); |
| if (firstChar == '?' || firstChar == ';') { |
| final char prefix; |
| final char separator; |
| final String emptyValueAssignment; |
| if (firstChar == '?') { |
| // query |
| prefix = '?'; |
| separator = '&'; |
| emptyValueAssignment = "="; |
| } else { |
| // matrix |
| prefix = ';'; |
| separator = ';'; |
| emptyValueAssignment = ""; |
| } |
| |
| int index = builder.length(); |
| String[] variables = variableName.substring(1).split(", ?"); |
| for (String variable : variables) { |
| try { |
| String value = valueStrategy.valueFor(variable, m.group()); |
| if (value != null) { |
| if (index != builder.length()) { |
| builder.append(separator); |
| } |
| |
| builder.append(variable); |
| if (value.isEmpty()) { |
| builder.append(emptyValueAssignment); |
| } else { |
| builder.append('='); |
| builder.append(value); |
| } |
| } |
| } catch (IllegalArgumentException ex) { |
| // no value found => ignore the variable |
| } |
| } |
| |
| if (index != builder.length() && (index == 0 || builder.charAt(index - 1) != prefix)) { |
| builder.insert(index, prefix); |
| } |
| } else { |
| String value = valueStrategy.valueFor(variableName, m.group()); |
| |
| if (value != null) { |
| builder.append(value); |
| } |
| } |
| |
| i = m.end(); |
| } |
| builder.append(normalizedTemplate, i, normalizedTemplate.length()); |
| } |
| |
| @Override |
| public final String toString() { |
| return pattern.toString(); |
| } |
| |
| /** |
| * Hash code is calculated from String of the regular expression |
| * generated from the template. |
| * |
| * @return the hash code. |
| */ |
| @Override |
| public final int hashCode() { |
| return pattern.hashCode(); |
| } |
| |
| /** |
| * Equality is calculated from the String of the regular expression |
| * generated from the templates. |
| * |
| * @param o the reference object with which to compare. |
| * @return true if equals, otherwise false. |
| */ |
| @Override |
| public final boolean equals(Object o) { |
| if (o instanceof UriTemplate) { |
| UriTemplate that = (UriTemplate) o; |
| return this.pattern.equals(that.pattern); |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Construct a URI from the component parts each of which may contain |
| * template variables. |
| * <p> |
| * A template values is an Object instance MUST support the toString() |
| * method to convert the template value to a String instance. |
| * </p> |
| * |
| * @param scheme the URI scheme component. |
| * @param authority the URI authority component. |
| * @param userInfo the URI user info component. |
| * @param host the URI host component. |
| * @param port the URI port component. |
| * @param path the URI path component. |
| * @param query the URI query component. |
| * @param fragment the URI fragment component. |
| * @param values the template variable to value map. |
| * @param encode if true encode a template value according to the correspond |
| * component type of the associated template variable, otherwise |
| * contextually encode the template value. |
| * @param encodeSlashInPath if {@code true}, the slash ({@code '/'}) characters |
| * in parameter values will be encoded if the template |
| * is placed in the URI path component, otherwise the slash |
| * characters will not be encoded in path templates. |
| * @return a URI. |
| */ |
| public static String createURI( |
| final String scheme, String authority, |
| final String userInfo, final String host, final String port, |
| final String path, final String query, final String fragment, |
| final Map<String, ?> values, final boolean encode, final boolean encodeSlashInPath) { |
| |
| Map<String, String> stringValues = new HashMap<String, String>(); |
| for (Map.Entry<String, ?> e : values.entrySet()) { |
| if (e.getValue() != null) { |
| stringValues.put(e.getKey(), e.getValue().toString()); |
| } |
| } |
| |
| return createURIWithStringValues(scheme, authority, |
| userInfo, host, port, path, query, fragment, |
| stringValues, encode, encodeSlashInPath); |
| } |
| |
| /** |
| * Construct a URI from the component parts each of which may contain |
| * template variables. |
| * <p> |
| * A template value is an Object instance that MUST support the toString() |
| * method to convert the template value to a String instance. |
| * </p> |
| * |
| * @param scheme the URI scheme component. |
| * @param authority the URI authority info component. |
| * @param userInfo the URI user info component. |
| * @param host the URI host component. |
| * @param port the URI port component. |
| * @param path the URI path component. |
| * @param query the URI query component. |
| * @param fragment the URI fragment component. |
| * @param values the template variable to value map. |
| * @param encode if true encode a template value according to the correspond |
| * component type of the associated template variable, otherwise |
| * contextually encode the template value. |
| * @param encodeSlashInPath if {@code true}, the slash ({@code '/'}) characters |
| * in parameter values will be encoded if the template |
| * is placed in the URI path component, otherwise the slash |
| * characters will not be encoded in path templates. |
| * @return a URI. |
| */ |
| public static String createURIWithStringValues( |
| final String scheme, final String authority, |
| final String userInfo, final String host, final String port, |
| final String path, final String query, final String fragment, |
| final Map<String, ?> values, final boolean encode, final boolean encodeSlashInPath) { |
| |
| return createURIWithStringValues( |
| scheme, authority, userInfo, host, port, path, query, fragment, EMPTY_VALUES, encode, encodeSlashInPath, values); |
| } |
| |
| /** |
| * Construct a URI from the component parts each of which may contain |
| * template variables. |
| * <p> |
| * The template values are an array of Object and each Object instance |
| * MUST support the toString() method to convert the template value to |
| * a String instance. |
| * </p> |
| * |
| * @param scheme the URI scheme component. |
| * @param authority the URI authority component. |
| * @param userInfo the URI user info component. |
| * @param host the URI host component. |
| * @param port the URI port component. |
| * @param path the URI path component. |
| * @param query the URI query component. |
| * @param fragment the URI fragment component. |
| * @param values the array of template values. |
| * @param encode if true encode a template value according to the correspond |
| * component type of the associated template variable, otherwise |
| * contextually encode the template value. |
| * @param encodeSlashInPath if {@code true}, the slash ({@code '/'}) characters |
| * in parameter values will be encoded if the template |
| * is placed in the URI path component, otherwise the slash |
| * characters will not be encoded in path templates. |
| * @return a URI. |
| */ |
| public static String createURI( |
| final String scheme, String authority, |
| final String userInfo, final String host, final String port, |
| final String path, final String query, final String fragment, |
| final Object[] values, final boolean encode, final boolean encodeSlashInPath) { |
| |
| String[] stringValues = new String[values.length]; |
| for (int i = 0; i < values.length; i++) { |
| if (values[i] != null) { |
| stringValues[i] = values[i].toString(); |
| } |
| } |
| |
| return createURIWithStringValues( |
| scheme, authority, |
| userInfo, host, port, path, query, fragment, |
| stringValues, encode, encodeSlashInPath); |
| } |
| |
| /** |
| * Construct a URI from the component parts each of which may contain |
| * template variables. |
| * |
| * @param scheme the URI scheme component. |
| * @param authority the URI authority component. |
| * @param userInfo the URI user info component. |
| * @param host the URI host component. |
| * @param port the URI port component. |
| * @param path the URI path component. |
| * @param query the URI query component. |
| * @param fragment the URI fragment component. |
| * @param values the array of template values. |
| * @param encode if true encode a template value according to the correspond |
| * component type of the associated template variable, otherwise |
| * contextually encode the template value. |
| * @param encodeSlashInPath if {@code true}, the slash ({@code '/'}) characters |
| * in parameter values will be encoded if the template |
| * is placed in the URI path component, otherwise the slash |
| * characters will not be encoded in path templates. |
| * @return a URI. |
| */ |
| public static String createURIWithStringValues( |
| final String scheme, final String authority, |
| final String userInfo, final String host, final String port, |
| final String path, final String query, final String fragment, |
| final String[] values, final boolean encode, final boolean encodeSlashInPath) { |
| |
| final Map<String, Object> mapValues = new HashMap<String, Object>(); |
| return createURIWithStringValues( |
| scheme, authority, userInfo, host, port, path, query, fragment, values, encode, encodeSlashInPath, mapValues); |
| } |
| |
| private static String createURIWithStringValues( |
| final String scheme, final String authority, final String userInfo, final String host, final String port, |
| final String path, final String query, final String fragment, final String[] values, final boolean encode, |
| final boolean encodeSlashInPath, final Map<String, ?> mapValues) { |
| |
| final StringBuilder sb = new StringBuilder(); |
| int offset = 0; |
| |
| if (scheme != null) { |
| offset = createUriComponent(UriComponent.Type.SCHEME, scheme, values, |
| offset, false, mapValues, sb); |
| sb.append(':'); |
| } |
| |
| boolean hasAuthority = false; |
| if (notEmpty(userInfo) || notEmpty(host) || notEmpty(port)) { |
| hasAuthority = true; |
| sb.append("//"); |
| |
| if (notEmpty(userInfo)) { |
| offset = createUriComponent(UriComponent.Type.USER_INFO, userInfo, values, |
| offset, encode, mapValues, sb); |
| sb.append('@'); |
| } |
| |
| if (notEmpty(host)) { |
| // TODO check IPv6 address |
| offset = createUriComponent(UriComponent.Type.HOST, host, values, |
| offset, encode, mapValues, sb); |
| } |
| |
| if (notEmpty(port)) { |
| sb.append(':'); |
| offset = createUriComponent(UriComponent.Type.PORT, port, values, |
| offset, false, mapValues, sb); |
| } |
| } else if (notEmpty(authority)) { |
| hasAuthority = true; |
| sb.append("//"); |
| |
| offset = createUriComponent(UriComponent.Type.AUTHORITY, authority, values, |
| offset, encode, mapValues, sb); |
| } |
| |
| if (notEmpty(path) || notEmpty(query) || notEmpty(fragment)) { |
| // make sure we append at least the root path if only query or fragment is present |
| if (hasAuthority && (path == null || path.isEmpty() || path.charAt(0) != '/')) { |
| sb.append('/'); |
| } |
| |
| if (notEmpty(path)) { |
| // path template values are treated as path segments unless encodeSlashInPath is false. |
| UriComponent.Type t = (encodeSlashInPath) ? UriComponent.Type.PATH_SEGMENT : UriComponent.Type.PATH; |
| |
| offset = createUriComponent(t, path, values, |
| offset, encode, mapValues, sb); |
| } |
| |
| if (notEmpty(query)) { |
| sb.append('?'); |
| offset = createUriComponent(UriComponent.Type.QUERY_PARAM, query, values, |
| offset, encode, mapValues, sb); |
| } |
| |
| if (notEmpty(fragment)) { |
| sb.append('#'); |
| createUriComponent(UriComponent.Type.FRAGMENT, fragment, values, |
| offset, encode, mapValues, sb); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private static boolean notEmpty(String string) { |
| return string != null && !string.isEmpty(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static int createUriComponent(final UriComponent.Type componentType, |
| String template, |
| final String[] values, |
| final int valueOffset, |
| final boolean encode, |
| final Map<String, ?> _mapValues, |
| final StringBuilder b) { |
| |
| final Map<String, Object> mapValues = (Map<String, Object>) _mapValues; |
| |
| if (template.indexOf('{') == -1) { |
| b.append(template); |
| return valueOffset; |
| } |
| |
| // Find all template variables |
| template = new UriTemplateParser(template).getNormalizedTemplate(); |
| |
| |
| class ValuesFromArrayStrategy implements TemplateValueStrategy { |
| private int offset = valueOffset; |
| |
| @Override |
| public String valueFor(String templateVariable, String matchedGroup) { |
| |
| Object value = mapValues.get(templateVariable); |
| if (value == null && offset < values.length) { |
| value = values[offset++]; |
| mapValues.put(templateVariable, value); |
| } |
| if (value == null) { |
| throw new IllegalArgumentException( |
| String.format("The template variable '%s' has no value", templateVariable)); |
| } |
| if (encode) { |
| return UriComponent.encode(value.toString(), componentType); |
| } else { |
| return UriComponent.contextualEncode(value.toString(), componentType); |
| } |
| } |
| } |
| ValuesFromArrayStrategy cs = new ValuesFromArrayStrategy(); |
| resolveTemplate(template, b, cs); |
| |
| return cs.offset; |
| } |
| |
| |
| /** |
| * Resolves template variables in the given {@code template} from {@code _mapValues}. Resolves only these variables which are |
| * defined in the {@code _mapValues} leaving other variables unchanged. |
| * |
| * @param type Type of the {@code template} (port, path, query, ...). |
| * @param template Input uri component to resolve. |
| * @param encode True if template values from {@code _mapValues} should be percent encoded. |
| * @param _mapValues Map with template variables as keys and template values as values. None of them should be null. |
| * @return String with resolved template variables. |
| * |
| * @throws IllegalArgumentException when {@code _mapValues} value is null. |
| */ |
| @SuppressWarnings("unchecked") |
| public static String resolveTemplateValues(final UriComponent.Type type, |
| String template, |
| final boolean encode, |
| final Map<String, ?> _mapValues) { |
| |
| if (template == null || template.isEmpty() || template.indexOf('{') == -1) { |
| return template; |
| } |
| |
| final Map<String, Object> mapValues = (Map<String, Object>) _mapValues; |
| |
| // Find all template variables |
| template = new UriTemplateParser(template).getNormalizedTemplate(); |
| |
| StringBuilder sb = new StringBuilder(); |
| resolveTemplate(template, sb, new TemplateValueStrategy() { |
| @Override |
| public String valueFor(String templateVariable, String matchedGroup) { |
| |
| Object value = mapValues.get(templateVariable); |
| |
| if (value != null) { |
| if (encode) { |
| value = UriComponent.encode(value.toString(), type); |
| } else { |
| value = UriComponent.contextualEncode(value.toString(), type); |
| } |
| return value.toString(); |
| } else { |
| if (mapValues.containsKey(templateVariable)) { |
| throw new IllegalArgumentException( |
| String.format("The value associated of the template value map for key '%s' is 'null'.", |
| templateVariable) |
| ); |
| } |
| |
| return matchedGroup; |
| } |
| } |
| }); |
| |
| return sb.toString(); |
| } |
| } |