Refine PathContainer.Segment value representation

Segment.value() now returns the actual original path segment value
including path parameters while semicolonContent() is removed.

valueDecoded() is renamed to valueToMatch() to reflect it is the value
for pattern matching which is not only decoded but also has path
parameters removed.
This commit is contained in:
Rossen Stoyanchev 2017-07-11 06:35:59 +02:00
parent 2f17c5f3b6
commit 1d201a55db
9 changed files with 46 additions and 75 deletions

View File

@ -119,14 +119,15 @@ class DefaultPathContainer implements PathContainer {
private static PathContainer.Segment parsePathSegment(String input, Charset charset) {
int index = input.indexOf(';');
if (index == -1) {
String inputDecoded = StringUtils.uriDecode(input, charset);
return new DefaultPathSegment(input, inputDecoded, "", EMPTY_MAP);
String valueToMatch = StringUtils.uriDecode(input, charset);
return new DefaultPathSegment(input, valueToMatch, EMPTY_MAP);
}
else {
String valueToMatch = StringUtils.uriDecode(input.substring(0, index), charset);
String pathParameterContent = input.substring(index);
MultiValueMap<String, String> parameters = parseParams(pathParameterContent, charset);
return new DefaultPathSegment(input, valueToMatch, parameters);
}
String value = input.substring(0, index);
String valueDecoded = StringUtils.uriDecode(value, charset);
String semicolonContent = input.substring(index);
MultiValueMap<String, String> parameters = parseParams(semicolonContent, charset);
return new DefaultPathSegment(value, valueDecoded, semicolonContent, parameters);
}
private static MultiValueMap<String, String> parseParams(String input, Charset charset) {
@ -189,22 +190,17 @@ class DefaultPathContainer implements PathContainer {
private final String value;
private final String valueDecoded;
private final String valueToMatch;
private final char[] valueDecodedChars;
private final String semicolonContent;
private final char[] valueToMatchAsChars;
private final MultiValueMap<String, String> parameters;
DefaultPathSegment(String value, String valueDecoded, String semicolonContent,
MultiValueMap<String, String> params) {
DefaultPathSegment(String value, String valueToMatch, MultiValueMap<String, String> params) {
Assert.isTrue(!value.contains("/"), () -> "Invalid path segment value: " + value);
this.value = value;
this.valueDecoded = valueDecoded;
this.valueDecodedChars = valueDecoded.toCharArray();
this.semicolonContent = semicolonContent;
this.valueToMatch = valueToMatch;
this.valueToMatchAsChars = valueToMatch.toCharArray();
this.parameters = CollectionUtils.unmodifiableMultiValueMap(params);
}
@ -214,18 +210,13 @@ class DefaultPathContainer implements PathContainer {
}
@Override
public String valueDecoded() {
return this.valueDecoded;
public String valueToMatch() {
return this.valueToMatch;
}
@Override
public char[] valueDecodedChars() {
return this.valueDecodedChars;
}
@Override
public String semicolonContent() {
return this.semicolonContent;
public char[] valueToMatchAsChars() {
return this.valueToMatchAsChars;
}
@Override
@ -241,26 +232,16 @@ class DefaultPathContainer implements PathContainer {
if (other == null || getClass() != other.getClass()) {
return false;
}
DefaultPathSegment segment = (DefaultPathSegment) other;
return (this.value.equals(segment.value) &&
this.semicolonContent.equals(segment.semicolonContent) &&
this.parameters.equals(segment.parameters));
return this.value.equals(((DefaultPathSegment) other).value);
}
@Override
public int hashCode() {
int result = this.value.hashCode();
result = 31 * result + this.semicolonContent.hashCode();
result = 31 * result + this.parameters.hashCode();
return result;
return this.value.hashCode();
}
public String toString() {
return "[value='" + this.value + "\', " +
"semicolonContent='" + this.semicolonContent + "\', " +
"parameters=" + this.parameters + "']";
}
return "[value='" + this.value + "', parameters=" + this.parameters + "']"; }
}
}

View File

@ -65,9 +65,6 @@ class DefaultRequestPath implements RequestPath {
for (int i=0; i < path.elements().size(); i++) {
PathContainer.Element element = path.elements().get(i);
counter += element.value().length();
if (element instanceof PathContainer.Segment) {
counter += ((Segment) element).semicolonContent().length();
}
if (length == counter) {
return path.subPath(0, i + 1);
}

View File

@ -104,21 +104,16 @@ public interface PathContainer {
interface Segment extends Element {
/**
* Return the path segment {@link #value()} decoded.
* Return the path segment value to use for pattern matching purposes.
* This may differ from {@link #value()} such as being decoded, without
* path parameters, etc.
*/
String valueDecoded();
String valueToMatch();
/**
* Variant of {@link #valueDecoded()} as a {@code char[]}.
* Variant of {@link #valueToMatch()} as a {@code char[]}.
*/
char[] valueDecodedChars();
/**
* Return the portion of the path segment after and including the first
* ";" (semicolon) representing path parameters. The actual parsed
* parameters if any can be obtained via {@link #parameters()}.
*/
String semicolonContent();
char[] valueToMatchAsChars();
/**
* Path parameters parsed from the path segment.

View File

@ -86,7 +86,7 @@ class CaptureTheRestPathElement extends PathElement {
for (int i = fromSegment, max = pathElements.size(); i < max; i++) {
Element element = pathElements.get(i);
if (element instanceof Segment) {
buf.append(((Segment)element).valueDecoded());
buf.append(((Segment)element).valueToMatch());
}
else {
buf.append(element.value());

View File

@ -62,13 +62,13 @@ class LiteralPathElement extends PathElement {
if (!(element instanceof Segment)) {
return false;
}
String value = ((Segment)element).valueDecoded();
String value = ((Segment)element).valueToMatch();
if (value.length() != len) {
// Not enough data to match this path element
return false;
}
char[] data = ((Segment)element).valueDecodedChars();
char[] data = ((Segment)element).valueToMatchAsChars();
if (this.caseSensitive) {
for (int i = 0; i < len; i++) {
if (data[i] != this.text[i]) {

View File

@ -683,7 +683,7 @@ public class PathPattern implements Comparable<PathPattern> {
String pathElementValue(int pathIndex) {
Element element = (pathIndex < pathLength) ? pathElements.get(pathIndex) : null;
if (element instanceof Segment) {
return ((Segment)element).valueDecoded();
return ((Segment)element).valueToMatch();
}
return "";
}

View File

@ -68,13 +68,13 @@ class SingleCharWildcardedPathElement extends PathElement {
if (!(element instanceof Segment)) {
return false;
}
String value = ((Segment)element).valueDecoded();
String value = ((Segment)element).valueToMatch();
if (value.length() != len) {
// Not enough data to match this path element
return false;
}
char[] data = ((Segment)element).valueDecodedChars();
char[] data = ((Segment)element).valueToMatchAsChars();
if (this.caseSensitive) {
for (int i = 0; i < len; i++) {
char ch = this.text[i];

View File

@ -50,7 +50,7 @@ class WildcardPathElement extends PathElement {
// Should not match a separator
return false;
}
segmentData = ((Segment)element).valueDecoded();
segmentData = ((Segment)element).valueToMatch();
pathIndex++;
}

View File

@ -38,14 +38,14 @@ public class DefaultPathContainerTests {
@Test
public void pathSegment() throws Exception {
// basic
testPathSegment("cars", "", "cars", "cars", new LinkedMultiValueMap<>());
testPathSegment("cars", "cars", new LinkedMultiValueMap<>());
// empty
testPathSegment("", "", "", "", new LinkedMultiValueMap<>());
testPathSegment("", "", new LinkedMultiValueMap<>());
// spaces
testPathSegment("%20%20", "", "%20%20", " ", new LinkedMultiValueMap<>());
testPathSegment("%20a%20", "", "%20a%20", " a ", new LinkedMultiValueMap<>());
testPathSegment("%20%20", " ", new LinkedMultiValueMap<>());
testPathSegment("%20a%20", " a ", new LinkedMultiValueMap<>());
}
@Test
@ -56,30 +56,29 @@ public class DefaultPathContainerTests {
params.add("colors", "blue");
params.add("colors", "green");
params.add("year", "2012");
testPathSegment("cars", ";colors=red,blue,green;year=2012", "cars", "cars", params);
testPathSegment("cars;colors=red,blue,green;year=2012", "cars", params);
// trailing semicolon
params = new LinkedMultiValueMap<>();
params.add("p", "1");
testPathSegment("path", ";p=1;", "path", "path", params);
testPathSegment("path;p=1;", "path", params);
// params with spaces
params = new LinkedMultiValueMap<>();
params.add("param name", "param value");
testPathSegment("path", ";param%20name=param%20value;%20", "path", "path", params);
testPathSegment("path;param%20name=param%20value;%20", "path", params);
// empty params
params = new LinkedMultiValueMap<>();
params.add("p", "1");
testPathSegment("path", ";;;%20;%20;p=1;%20", "path", "path", params);
testPathSegment("path;;;%20;%20;p=1;%20", "path", params);
}
private void testPathSegment(String rawValue, String semicolonContent,
String value, String valueDecoded, MultiValueMap<String, String> params) {
private void testPathSegment(String rawValue, String valueToMatch, MultiValueMap<String, String> params) {
PathContainer container = DefaultPathContainer.parsePath(rawValue + semicolonContent, UTF_8);
PathContainer container = DefaultPathContainer.parsePath(rawValue, UTF_8);
if ("".equals(value)) {
if ("".equals(rawValue)) {
assertEquals(0, container.elements().size());
return;
}
@ -87,9 +86,8 @@ public class DefaultPathContainerTests {
assertEquals(1, container.elements().size());
PathContainer.Segment segment = (PathContainer.Segment) container.elements().get(0);
assertEquals("value: '" + rawValue + "'", value, segment.value());
assertEquals("valueDecoded: '" + rawValue + "'", valueDecoded, segment.valueDecoded());
assertEquals("semicolonContent: '" + rawValue + "'", semicolonContent, segment.semicolonContent());
assertEquals("value: '" + rawValue + "'", rawValue, segment.value());
assertEquals("valueToMatch: '" + rawValue + "'", valueToMatch, segment.valueToMatch());
assertEquals("params: '" + rawValue + "'", params, segment.parameters());
}