diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 8142936b1b4..09eef5bf178 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -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 queryParams = new LinkedMultiValueMap(); @@ -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 componentBuilders = new LinkedList(); + private final LinkedList builders = new LinkedList(); 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 getLastBuilder(Class 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 components = new ArrayList(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; + } } } diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index d8f7f4d34dc..85a65434359 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -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()); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java index d2678abddce..abdc8049c74 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -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 { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java index 057d827d1b0..e669adf07de 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java @@ -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); + } + }