From 6c58da0e55f9d7ebd0f4d7268b63ade28eb794fb Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 9 Sep 2011 10:49:10 +0000 Subject: [PATCH] SPR-5973: Added UriComponents type --- .../web/util/UriComponents.java | 403 ++++++++++++++++++ .../web/util/UriComponentsTests.java | 73 ++++ 2 files changed, 476 insertions(+) create mode 100644 org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java create mode 100644 org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java new file mode 100644 index 0000000000..fe6c705a3d --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/util/UriComponents.java @@ -0,0 +1,403 @@ +/* + * Copyright 2002-2011 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.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +/** + * Represents the components that make up a URI, mapping component type to string values. Contains convenience getters + * and setters for all components, as well as the regular {@link Map} implementation. + * + *

This mapping does not contain mappings for {@link UriComponent#PATH_SEGMENT} or nor {@link + * UriComponent#QUERY_PARAM}, since those components can occur multiple times in the URI. Instead, one can use {@link + * #getPathSegments()} or {@link #getQueryParams()} respectively. + * + * @author Arjen Poutsma + * @since 3.1 + */ +public class UriComponents implements Map { + + private static final String PATH_DELIMITER = "/"; + + private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)=?([^&=]+)?"); + + private final Map uriComponents; + + /** Constructs a new, empty instance of the {@code UriComponents} object. */ + public UriComponents() { + this.uriComponents = new EnumMap(UriComponent.class); + } + + /** + * Creates an instance of the {@code UriComponents} object that contains the given component. + * + * @param uriComponents the component to initialize with + */ + public UriComponents(Map uriComponents) { + Assert.notNull(uriComponents, "'uriComponents' must not be null"); + this.uriComponents = new EnumMap(uriComponents); + } + + // convenience properties + + /** + * Returns the scheme. + * + * @return the scheme. Can be {@code null}. + */ + public String getScheme() { + return get(UriComponent.SCHEME); + } + + /** + * Sets the scheme. + * + * @param scheme the scheme. Can be {@code null}. + */ + public void setScheme(String scheme) { + put(UriComponent.SCHEME, scheme); + } + + /** + * Returns the authority. + * + * @return the authority. Can be {@code null}. + */ + public String getAuthority() { + return get(UriComponent.AUTHORITY); + } + + /** + * Sets the authority. + * + * @param authority the authority. Can be {@code null}. + */ + public void setAuthority(String authority) { + put(UriComponent.AUTHORITY, authority); + } + + /** + * Returns the user info. + * + * @return the user info. Can be {@code null}. + */ + public String getUserInfo() { + return get(UriComponent.USER_INFO); + } + + /** + * Sets the user info + * + * @param userInfo the user info. Can be {@code null} + */ + public void setUserInfo(String userInfo) { + put(UriComponent.USER_INFO, userInfo); + } + + /** + * Returns the host. + * + * @return the host. Can be {@code null}. + */ + public String getHost() { + return get(UriComponent.HOST); + } + + /** + * Sets the host. + * + * @param host the host. Can be {@code null}. + */ + public void setHost(String host) { + put(UriComponent.HOST, host); + } + + /** + * Returns the port as string. + * + * @return the port as string. Can be {@code null}. + */ + public String getPort() { + return get(UriComponent.PORT); + } + + /** + * Sets the port as string. + * + * @param port the port as string. Can be {@code null}. + */ + public void setPort(String port) { + put(UriComponent.PORT, port); + } + + /** + * Returns the port as integer. Returns {@code -1} if no port has been set. + * + * @return the port the port as integer + */ + public int getPortAsInteger() { + String port = getPort(); + return port != null ? Integer.parseInt(port) : -1; + } + + /** + * Sets the port as integer. A value < 0 resets the port to an empty value. + * + * @param port the port as integer + */ + public void setPortAsInteger(int port) { + String portString = port > -1 ? Integer.toString(port) : null; + put(UriComponent.PORT, portString); + } + + /** + * Returns the path. + * + * @return the path. Can be {@code null}. + */ + public String getPath() { + return get(UriComponent.PATH); + } + + /** + * Sets the path. + * + * @param path the path. Can be {@code null}. + */ + public void setPath(String path) { + put(UriComponent.PATH, path); + } + + /** + * Returns the list of path segments. + * + * @return the path segments. Empty if no path has been set. + */ + public List getPathSegments() { + String path = getPath(); + if (path != null) { + return Arrays.asList(StringUtils.tokenizeToStringArray(path, PATH_DELIMITER)); + } + else { + return Collections.emptyList(); + } + } + + /** + * Sets the path segments. An empty or {@code null} value resets the path to an empty value. + * + * @param pathSegments the path segments + */ + public void setPathSegments(List pathSegments) { + if (!CollectionUtils.isEmpty(pathSegments)) { + StringBuilder pathBuilder = new StringBuilder("/"); + for (Iterator iterator = pathSegments.iterator(); iterator.hasNext(); ) { + String pathSegment = iterator.next(); + pathBuilder.append(pathSegment); + if (iterator.hasNext()) { + pathBuilder.append('/'); + } + } + setPath(pathBuilder.toString()); + } + else { + setPath(null); + } + } + + /** + * Returns the query. + * + * @return the query. Can be {@code null}. + */ + public String getQuery() { + return get(UriComponent.QUERY); + } + + /** + * Sets the query. + * + * @param query the query. Can be {@code null}. + */ + public void setQuery(String query) { + put(UriComponent.QUERY, query); + } + + /** + * Returns the map of query parameters. + * + * @return the query parameters. Empty if no query has been set. + */ + public MultiValueMap getQueryParams() { + MultiValueMap result = new LinkedMultiValueMap(); + String query = getQuery(); + if (query != null) { + Matcher m = QUERY_PARAM_PATTERN.matcher(query); + while (m.find()) { + String name = m.group(1); + String value = m.group(2); + result.add(name, value); + } + } + return result; + } + + /** + * Sets the query parameters. An empty or {@code null} value resets the query to an empty value. + * + * @param queryParams the query parameters + */ + public void setQueryParams(MultiValueMap queryParams) { + if (!CollectionUtils.isEmpty(queryParams)) { + StringBuilder queryBuilder = new StringBuilder(); + for (Iterator>> entryIterator = queryParams.entrySet().iterator(); + entryIterator.hasNext(); ) { + Entry> entry = entryIterator.next(); + String name = entry.getKey(); + List values = entry.getValue(); + if (CollectionUtils.isEmpty(values) || (values.size() == 1 && values.get(0) == null)) { + queryBuilder.append(name); + } + else { + for (Iterator valueIterator = values.iterator(); valueIterator.hasNext(); ) { + String value = valueIterator.next(); + queryBuilder.append(name); + queryBuilder.append('='); + queryBuilder.append(value); + if (valueIterator.hasNext()) { + queryBuilder.append('&'); + } + } + } + if (entryIterator.hasNext()) { + queryBuilder.append('&'); + } + } + setQuery(queryBuilder.toString()); + } + else { + setQuery(null); + } + } + + /** + * Returns the fragment. + * + * @return the fragment. Can be {@code null}. + */ + public String getFragment() { + return get(UriComponent.FRAGMENT); + } + + /** + * Sets the fragment. + * + * @param fragment the fragment. Can be {@code null}. + */ + public void setFragment(String fragment) { + put(UriComponent.FRAGMENT, fragment); + } + + // Map implementation + + public int size() { + return this.uriComponents.size(); + } + + public boolean isEmpty() { + return this.uriComponents.isEmpty(); + } + + public boolean containsKey(Object key) { + return this.uriComponents.containsKey(key); + } + + public boolean containsValue(Object value) { + return this.uriComponents.containsValue(value); + } + + public String get(Object key) { + return this.uriComponents.get(key); + } + + public String put(UriComponent key, String value) { + return this.uriComponents.put(key, value); + } + + public String remove(Object key) { + return this.uriComponents.remove(key); + } + + public void putAll(Map m) { + this.uriComponents.putAll(m); + } + + public void clear() { + this.uriComponents.clear(); + } + + public Set keySet() { + return this.uriComponents.keySet(); + } + + public Collection values() { + return this.uriComponents.values(); + } + + public Set> entrySet() { + return this.uriComponents.entrySet(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof UriComponents) { + UriComponents other = (UriComponents) o; + return this.uriComponents.equals(other.uriComponents); + } + return false; + } + + @Override + public int hashCode() { + return this.uriComponents.hashCode(); + } + + @Override + public String toString() { + return this.uriComponents.toString(); + } + + +} diff --git a/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java new file mode 100644 index 0000000000..df8f51b807 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2011 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.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import static org.junit.Assert.*; + +/** @author Arjen Poutsma */ +public class UriComponentsTests { + + private UriComponents components; + + @Before + public void createComponents() { + components = new UriComponents(); + } + + @Test + public void pathSegments() { + String path = "/foo/bar"; + components.setPath(path); + List expected = Arrays.asList("foo", "bar"); + + List pathSegments = components.getPathSegments(); + assertEquals(expected, pathSegments); + + components.setPath(null); + + components.setPathSegments(expected); + assertEquals(path, components.getPath()); + } + + @Test + public void queryParams() { + String query = "foo=bar&foo=baz&qux"; + components.setQuery(query); + MultiValueMap expected = new LinkedMultiValueMap(1); + expected.put("foo", Arrays.asList("bar", "baz")); + expected.set("qux", null); + + MultiValueMap result = components.getQueryParams(); + assertEquals(expected, result); + + components.setQuery(null); + + components.setQueryParams(expected); + assertEquals(query, components.getQuery()); + + } + +}