Add ability to clone to UriComponentsBuilder hierarchy

Issue: SPR-12494
This commit is contained in:
Rossen Stoyanchev 2014-12-05 12:14:16 -05:00
parent 2fccf3ff44
commit 189ec75789
4 changed files with 127 additions and 14 deletions

View File

@ -54,7 +54,7 @@ import org.springframework.web.util.HierarchicalUriComponents.PathComponent;
* @see #fromPath(String)
* @see #fromUri(URI)
*/
public class UriComponentsBuilder {
public class UriComponentsBuilder implements Cloneable {
private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?");
@ -98,7 +98,7 @@ public class UriComponentsBuilder {
private String port;
private CompositePathComponentBuilder pathBuilder = new CompositePathComponentBuilder();
private CompositePathComponentBuilder pathBuilder;
private final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
@ -112,6 +112,22 @@ public class UriComponentsBuilder {
* @see #fromUri(URI)
*/
protected UriComponentsBuilder() {
this.pathBuilder = new CompositePathComponentBuilder();
}
/**
* Create a deep copy of the given UriComponentsBuilder.
* @param other the other builder to copy from
*/
protected UriComponentsBuilder(UriComponentsBuilder other) {
this.scheme = other.scheme;
this.ssp = other.ssp;
this.userInfo = other.userInfo;
this.host = other.host;
this.port = other.port;
this.pathBuilder = (CompositePathComponentBuilder) other.pathBuilder.clone();
this.queryParams.putAll(other.queryParams);
this.fragment = other.fragment;
}
@ -627,16 +643,23 @@ public class UriComponentsBuilder {
return this;
}
@Override
protected Object clone() {
return new UriComponentsBuilder(this);
}
private interface PathComponentBuilder {
private interface PathComponentBuilder extends Cloneable {
PathComponent build();
Object clone();
}
private static class CompositePathComponentBuilder implements PathComponentBuilder {
private final LinkedList<PathComponentBuilder> componentBuilders = new LinkedList<PathComponentBuilder>();
private final LinkedList<PathComponentBuilder> builders = new LinkedList<PathComponentBuilder>();
public CompositePathComponentBuilder() {
}
@ -651,7 +674,7 @@ public class UriComponentsBuilder {
FullPathComponentBuilder fpBuilder = getLastBuilder(FullPathComponentBuilder.class);
if (psBuilder == null) {
psBuilder = new PathSegmentComponentBuilder();
this.componentBuilders.add(psBuilder);
this.builders.add(psBuilder);
if (fpBuilder != null) {
fpBuilder.removeTrailingSlash();
}
@ -669,7 +692,7 @@ public class UriComponentsBuilder {
}
if (fpBuilder == null) {
fpBuilder = new FullPathComponentBuilder();
this.componentBuilders.add(fpBuilder);
this.builders.add(fpBuilder);
}
fpBuilder.append(path);
}
@ -677,8 +700,8 @@ public class UriComponentsBuilder {
@SuppressWarnings("unchecked")
private <T> T getLastBuilder(Class<T> builderClass) {
if (!this.componentBuilders.isEmpty()) {
PathComponentBuilder last = this.componentBuilders.getLast();
if (!this.builders.isEmpty()) {
PathComponentBuilder last = this.builders.getLast();
if (builderClass.isInstance(last)) {
return (T) last;
}
@ -688,9 +711,9 @@ public class UriComponentsBuilder {
@Override
public PathComponent build() {
int size = this.componentBuilders.size();
int size = this.builders.size();
List<PathComponent> components = new ArrayList<PathComponent>(size);
for (PathComponentBuilder componentBuilder : this.componentBuilders) {
for (PathComponentBuilder componentBuilder : this.builders) {
PathComponent pathComponent = componentBuilder.build();
if (pathComponent != null) {
components.add(pathComponent);
@ -704,6 +727,15 @@ public class UriComponentsBuilder {
}
return new HierarchicalUriComponents.PathComponentComposite(components);
}
@Override
public Object clone() {
CompositePathComponentBuilder compositeBuilder = new CompositePathComponentBuilder();
for (PathComponentBuilder builder : this.builders) {
compositeBuilder.builders.add((PathComponentBuilder) builder.clone());
}
return compositeBuilder;
}
}
@ -737,6 +769,13 @@ public class UriComponentsBuilder {
this.path.deleteCharAt(index);
}
}
@Override
public Object clone() {
FullPathComponentBuilder builder = new FullPathComponentBuilder();
builder.append(this.path.toString());
return builder;
}
}
@ -757,6 +796,13 @@ public class UriComponentsBuilder {
return (this.pathSegments.isEmpty() ? null :
new HierarchicalUriComponents.PathSegmentComponent(this.pathSegments));
}
@Override
public Object clone() {
PathSegmentComponentBuilder builder = new PathSegmentComponentBuilder();
builder.pathSegments.addAll(this.pathSegments);
return builder;
}
}
}

View File

@ -16,6 +16,14 @@
package org.springframework.web.util;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
@ -23,14 +31,10 @@ import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
* @author Phillip Webb
@ -436,4 +440,28 @@ public class UriComponentsBuilderTests {
assertThat(components.getFragment(), is(nullValue()));
assertThat(components.toString(), equalTo("/example"));
}
@Test
public void testClone() throws URISyntaxException {
UriComponentsBuilder builder1 = UriComponentsBuilder.newInstance();
builder1.scheme("http").host("e1.com").path("/p1").pathSegment("ps1").queryParam("q1").fragment("f1");
UriComponentsBuilder builder2 = (UriComponentsBuilder) builder1.clone();
builder2.scheme("https").host("e2.com").path("p2").pathSegment("ps2").queryParam("q2").fragment("f2");
UriComponents result1 = builder1.build();
assertEquals("http", result1.getScheme());
assertEquals("e1.com", result1.getHost());
assertEquals("/p1/ps1", result1.getPath());
assertEquals("q1", result1.getQuery());
assertEquals("f1", result1.getFragment());
UriComponents result2 = builder2.build();
assertEquals("https", result2.getScheme());
assertEquals("e2.com", result2.getHost());
assertEquals("/p1/ps1/p2/ps2", result2.getPath());
assertEquals("q1&q2", result2.getQuery());
assertEquals("f2", result2.getFragment());
}
}

View File

@ -93,6 +93,26 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
}
/**
* Default constructor. Protected to prevent direct instantiation.
*
* @see #fromController(Class)
* @see #fromMethodName(Class, String, Object...)
* @see #fromMethodCall(Object)
* @see #fromMappingName(String)
* @see #fromMethod(java.lang.reflect.Method, Object...)
*/
protected MvcUriComponentsBuilder() {
}
/**
* Create a deep copy of the given MvcUriComponentsBuilder.
* @param other the other builder to copy from
*/
protected MvcUriComponentsBuilder(MvcUriComponentsBuilder other) {
super(other);
}
/**
* Create a {@link UriComponentsBuilder} from the mapping of a controller class
* and current request information including Servlet mapping. If the controller
@ -431,6 +451,11 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
}
}
@Override
protected Object clone() {
return new MvcUriComponentsBuilder(this);
}
private static class ControllerMethodInvocationInterceptor
implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {

View File

@ -51,6 +51,15 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
protected ServletUriComponentsBuilder() {
}
/**
* Create a deep copy of the given ServletUriComponentsBuilder.
* @param other the other builder to copy from
*/
protected ServletUriComponentsBuilder(ServletUriComponentsBuilder other) {
super(other);
this.originalPath = other.originalPath;
}
/**
* Prepare a builder from the host, port, scheme, and context path of the
* given HttpServletRequest.
@ -232,4 +241,9 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
return extension;
}
@Override
protected Object clone() {
return new ServletUriComponentsBuilder(this);
}
}