From 97917aa57d898e3e085beb0d17d26728813bb10c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 14 Jun 2017 17:43:56 -0400 Subject: [PATCH] Add PathSegmentContainer subPath extracting method --- .../reactive/DefaultPathSegmentContainer.java | 18 ++++++++++++++- .../server/reactive/DefaultRequestPath.java | 22 +++++++------------ .../server/reactive/PathSegmentContainer.java | 10 +++++++++ .../DefaultPathSegmentContainerTests.java | 14 ++++++++++++ 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultPathSegmentContainer.java b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultPathSegmentContainer.java index 97816ea5a9..b539217518 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultPathSegmentContainer.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultPathSegmentContainer.java @@ -19,6 +19,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -56,7 +57,7 @@ class DefaultPathSegmentContainer implements PathSegmentContainer { private final boolean trailingSlash; - DefaultPathSegmentContainer(String path, List segments) { + private DefaultPathSegmentContainer(String path, List segments) { this.path = path; this.absolute = path.startsWith("/"); this.pathSegments = Collections.unmodifiableList(segments); @@ -188,6 +189,21 @@ class DefaultPathSegmentContainer implements PathSegmentContainer { } } + static PathSegmentContainer subPath(PathSegmentContainer container, int fromIndex, int toIndex) { + List segments = container.pathSegments(); + if (fromIndex == 0 && toIndex == segments.size()) { + return container; + } + Assert.isTrue(fromIndex < toIndex, "fromIndex: " + fromIndex + " should be < toIndex " + toIndex); + Assert.isTrue(fromIndex >= 0 && fromIndex < segments.size(), "Invalid fromIndex: " + fromIndex); + Assert.isTrue(toIndex >= 0 && toIndex <= segments.size(), "Invalid toIndex: " + toIndex); + + List subList = segments.subList(fromIndex, toIndex); + String prefix = fromIndex > 0 || container.isAbsolute() ? "/" : ""; + String suffix = toIndex == segments.size() && container.hasTrailingSlash() ? "/" : ""; + String path = subList.stream().map(PathSegment::value).collect(Collectors.joining(prefix, "/", suffix)); + return new DefaultPathSegmentContainer(path, subList); + } private static class DefaultPathSegment implements PathSegment { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultRequestPath.java b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultRequestPath.java index 67a4892d75..60aec19648 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultRequestPath.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultRequestPath.java @@ -17,7 +17,6 @@ package org.springframework.http.server.reactive; import java.net.URI; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.List; import org.springframework.util.Assert; @@ -41,16 +40,15 @@ class DefaultRequestPath implements RequestPath { DefaultRequestPath(URI uri, String contextPath, Charset charset) { this.fullPath = PathSegmentContainer.parse(uri.getRawPath(), charset); this.contextPath = initContextPath(this.fullPath, contextPath); - this.pathWithinApplication = initPathWithinApplication(this.fullPath, this.contextPath); + this.pathWithinApplication = extractPathWithinApplication(this.fullPath, this.contextPath); } DefaultRequestPath(RequestPath requestPath, String contextPath) { this.fullPath = requestPath; this.contextPath = initContextPath(this.fullPath, contextPath); - this.pathWithinApplication = initPathWithinApplication(this.fullPath, this.contextPath); + this.pathWithinApplication = extractPathWithinApplication(this.fullPath, this.contextPath); } - private static PathSegmentContainer initContextPath(PathSegmentContainer path, String contextPath) { if (!StringUtils.hasText(contextPath) || "/".equals(contextPath)) { return DefaultPathSegmentContainer.EMPTY_PATH; @@ -62,14 +60,13 @@ class DefaultRequestPath implements RequestPath { int length = contextPath.length(); int counter = 0; - List result = new ArrayList<>(); - for (PathSegment pathSegment : path.pathSegments()) { - result.add(pathSegment); - counter += 1; // for '/' separators + for (int i=0; i < path.pathSegments().size(); i++) { + PathSegment pathSegment = path.pathSegments().get(i); + counter += 1; // for slash separators counter += pathSegment.value().length(); counter += pathSegment.semicolonContent().length(); if (length == counter) { - return new DefaultPathSegmentContainer(contextPath, result); + return DefaultPathSegmentContainer.subPath(path, 0, i + 1); } } @@ -78,13 +75,10 @@ class DefaultRequestPath implements RequestPath { " given path='" + path.value() + "'"); } - private static PathSegmentContainer initPathWithinApplication(PathSegmentContainer path, + private static PathSegmentContainer extractPathWithinApplication(PathSegmentContainer fullPath, PathSegmentContainer contextPath) { - String value = path.value().substring(contextPath.value().length()); - List pathSegments = new ArrayList<>(path.pathSegments()); - pathSegments.removeAll(contextPath.pathSegments()); - return new DefaultPathSegmentContainer(value, pathSegments); + return PathSegmentContainer.subPath(fullPath, contextPath.pathSegments().size()); } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/PathSegmentContainer.java b/spring-web/src/main/java/org/springframework/http/server/reactive/PathSegmentContainer.java index c5017af098..6fc677f288 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/PathSegmentContainer.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/PathSegmentContainer.java @@ -67,4 +67,14 @@ public interface PathSegmentContainer { return DefaultPathSegmentContainer.parsePath(path, encoding); } + /** + * Extract a sub-path starting at the given offset into the path segment list. + * @param path the path to extract from + * @param pathSegmentIndex the start index (inclusive) + * @return the sub-path + */ + static PathSegmentContainer subPath(PathSegmentContainer path, int pathSegmentIndex) { + return DefaultPathSegmentContainer.subPath(path, pathSegmentIndex, path.pathSegments().size()); + } + } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultPathSegmentContainerTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultPathSegmentContainerTests.java index a4a77e77f4..d1a62d5253 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultPathSegmentContainerTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/DefaultPathSegmentContainerTests.java @@ -27,6 +27,7 @@ import org.springframework.util.MultiValueMap; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; /** * Unit tests for {@link DefaultPathSegmentContainer}. @@ -121,4 +122,17 @@ public class DefaultPathSegmentContainerTests { assertEquals("hasTrailingSlash: '" + input + "'", trailingSlash, path.hasTrailingSlash()); } + @Test + public void subPath() throws Exception { + // basic + PathSegmentContainer path = PathSegmentContainer.parse("/a/b/c", UTF_8); + assertSame(path, PathSegmentContainer.subPath(path, 0)); + assertEquals("/b/c", PathSegmentContainer.subPath(path, 1).value()); + assertEquals("/c", PathSegmentContainer.subPath(path, 2).value()); + + // trailing slash + path = PathSegmentContainer.parse("/a/b/", UTF_8); + assertEquals("/b/", PathSegmentContainer.subPath(path, 1).value()); + } + }