Support optional trailing separator on path pattern matching

This commit adds the ability for path patterns to automatically
match a trailing separator (so there is no need to add two
variants of a pattern, one with and one without). This behaviour
is currently turned off but a simple tweak in PathPatternParser
can make it the default. If made default other parts of Spring
may need altering (simplifying hopefully) to cope with this.

Issue: SPR-15260
This commit is contained in:
Andy Clement
2017-04-19 11:59:21 -07:00
parent e93e49f268
commit cd86558811
13 changed files with 404 additions and 139 deletions

View File

@@ -38,6 +38,198 @@ import static org.junit.Assert.*;
*/
public class PathPatternMatcherTests {
@Test
public void basicMatching() {
checkMatches(null, null);
checkMatches("", "");
checkMatches("", null);
checkNoMatch("/abc", null);
checkMatches(null, "");
checkNoMatch(null, "/abc");
checkMatches("/", "/");
checkNoMatch("/", "/a");
checkMatches("f", "f");
checkMatches("/foo", "/foo");
checkMatches("/foo/", "/foo/");
checkMatches("/foo/bar", "/foo/bar");
checkMatches("foo/bar", "foo/bar");
checkMatches("/foo/bar/", "/foo/bar/");
checkMatches("foo/bar/", "foo/bar/");
checkMatches("/foo/bar/woo", "/foo/bar/woo");
checkNoMatch("foo", "foobar");
checkMatches("/foo/bar", "/foo/bar");
checkNoMatch("/foo/bar", "/foo/baz");
// TODO Need more tests for escaped separators in path patterns and paths?
checkMatches("/foo\\/bar", "/foo\\/bar"); // chain string is Separator(/) Literal(foo\) Separator(/) Literal(bar)
}
@Test
public void optionalTrailingSeparators() {
// LiteralPathElement
PathPattern pp = parse("/resource");
assertTrue(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
pp = parse("/resource/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
// SingleCharWildcardPathElement
pp = parse("/res?urce");
assertTrue(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
pp = parse("/res?urce/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
// CaptureVariablePathElement
pp = parse("/{var}");
assertTrue(pp.matches("/resource"));
assertEquals("resource",pp.matchAndExtract("/resource").get("var"));
assertTrue(pp.matches("/resource/"));
assertEquals("resource",pp.matchAndExtract("/resource/").get("var"));
assertFalse(pp.matches("/resource//"));
pp = parse("/{var}/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertEquals("resource",pp.matchAndExtract("/resource/").get("var"));
assertFalse(pp.matches("/resource//"));
// CaptureTheRestPathElement
pp = parse("/{*var}");
assertTrue(pp.matches("/resource"));
assertEquals("/resource",pp.matchAndExtract("/resource").get("var"));
assertTrue(pp.matches("/resource/"));
assertEquals("/resource/",pp.matchAndExtract("/resource/").get("var"));
assertTrue(pp.matches("/resource//"));
assertEquals("/resource//",pp.matchAndExtract("/resource//").get("var"));
assertTrue(pp.matches("//resource//"));
assertEquals("//resource//",pp.matchAndExtract("//resource//").get("var"));
// WildcardTheRestPathElement
pp = parse("/**");
assertTrue(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertTrue(pp.matches("/resource//"));
assertTrue(pp.matches("//resource//"));
// WildcardPathElement
pp = parse("/*");
assertTrue(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
pp = parse("/*/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
// RegexPathElement
pp = parse("/{var1}_{var2}");
assertTrue(pp.matches("/res1_res2"));
assertEquals("res1",pp.matchAndExtract("/res1_res2").get("var1"));
assertEquals("res2",pp.matchAndExtract("/res1_res2").get("var2"));
assertTrue(pp.matches("/res1_res2/"));
assertEquals("res1",pp.matchAndExtract("/res1_res2/").get("var1"));
assertEquals("res2",pp.matchAndExtract("/res1_res2/").get("var2"));
assertFalse(pp.matches("/res1_res2//"));
pp = parse("/{var1}_{var2}/");
assertFalse(pp.matches("/res1_res2"));
assertTrue(pp.matches("/res1_res2/"));
assertEquals("res1",pp.matchAndExtract("/res1_res2/").get("var1"));
assertEquals("res2",pp.matchAndExtract("/res1_res2/").get("var2"));
assertFalse(pp.matches("/res1_res2//"));
pp = parse("/{var1}*");
assertTrue(pp.matches("/a"));
assertTrue(pp.matches("/a/"));
assertFalse(pp.matches("/")); // no characters for var1
assertFalse(pp.matches("//")); // no characters for var1
// Now with trailing matching turned OFF
PathPatternParser parser = new PathPatternParser();
parser.setMatchOptionalTrailingSlash(false);
// LiteralPathElement
pp = parser.parse("/resource");
assertTrue(pp.matches("/resource"));
assertFalse(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
pp = parser.parse("/resource/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
// SingleCharWildcardPathElement
pp = parser.parse("/res?urce");
assertTrue(pp.matches("/resource"));
assertFalse(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
pp = parser.parse("/res?urce/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
// CaptureVariablePathElement
pp = parser.parse("/{var}");
assertTrue(pp.matches("/resource"));
assertEquals("resource",pp.matchAndExtract("/resource").get("var"));
assertFalse(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
pp = parser.parse("/{var}/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertEquals("resource",pp.matchAndExtract("/resource/").get("var"));
assertFalse(pp.matches("/resource//"));
// CaptureTheRestPathElement
pp = parser.parse("/{*var}");
assertTrue(pp.matches("/resource"));
assertEquals("/resource",pp.matchAndExtract("/resource").get("var"));
assertTrue(pp.matches("/resource/"));
assertEquals("/resource/",pp.matchAndExtract("/resource/").get("var"));
assertTrue(pp.matches("/resource//"));
assertEquals("/resource//",pp.matchAndExtract("/resource//").get("var"));
assertTrue(pp.matches("//resource//"));
assertEquals("//resource//",pp.matchAndExtract("//resource//").get("var"));
// WildcardTheRestPathElement
pp = parser.parse("/**");
assertTrue(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertTrue(pp.matches("/resource//"));
assertTrue(pp.matches("//resource//"));
// WildcardPathElement
pp = parser.parse("/*");
assertTrue(pp.matches("/resource"));
assertFalse(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
pp = parser.parse("/*/");
assertFalse(pp.matches("/resource"));
assertTrue(pp.matches("/resource/"));
assertFalse(pp.matches("/resource//"));
// RegexPathElement
pp = parser.parse("/{var1}_{var2}");
assertTrue(pp.matches("/res1_res2"));
assertEquals("res1",pp.matchAndExtract("/res1_res2").get("var1"));
assertEquals("res2",pp.matchAndExtract("/res1_res2").get("var2"));
assertFalse(pp.matches("/res1_res2/"));
assertFalse(pp.matches("/res1_res2//"));
pp = parser.parse("/{var1}_{var2}/");
assertFalse(pp.matches("/res1_res2"));
assertTrue(pp.matches("/res1_res2/"));
assertEquals("res1",pp.matchAndExtract("/res1_res2/").get("var1"));
assertEquals("res2",pp.matchAndExtract("/res1_res2/").get("var2"));
assertFalse(pp.matches("/res1_res2//"));
pp = parser.parse("/{var1}*");
assertTrue(pp.matches("/a"));
assertFalse(pp.matches("/a/"));
assertFalse(pp.matches("/")); // no characters for var1
assertFalse(pp.matches("//")); // no characters for var1
}
@Test
public void pathRemainderBasicCases_spr15336() {
// Cover all PathElement kinds
@@ -92,31 +284,6 @@ public class PathPatternMatcherTests {
assertNull(parse("").getPathRemaining(null).getPathRemaining());
}
@Test
public void basicMatching() {
checkMatches(null, null);
checkMatches("", "");
checkMatches("", null);
checkNoMatch("/abc", null);
checkMatches(null, "");
checkNoMatch(null, "/abc");
checkMatches("/", "/");
checkNoMatch("/", "/a");
checkMatches("f", "f");
checkMatches("/foo", "/foo");
checkMatches("/foo/", "/foo/");
checkMatches("/foo/bar", "/foo/bar");
checkMatches("foo/bar", "foo/bar");
checkMatches("/foo/bar/", "/foo/bar/");
checkMatches("foo/bar/", "foo/bar/");
checkMatches("/foo/bar/woo", "/foo/bar/woo");
checkNoMatch("foo", "foobar");
checkMatches("/foo/bar", "/foo/bar");
checkNoMatch("/foo/bar", "/foo/baz");
// TODO Need more tests for escaped separators in path patterns and paths?
checkMatches("/foo\\/bar", "/foo\\/bar"); // chain string is Separator(/) Literal(foo\) Separator(/) Literal(bar)
}
@Test
public void questionMarks() {
checkNoMatch("a", "ab");
@@ -144,38 +311,62 @@ public class PathPatternMatcherTests {
checkCapture("/customer/{*something}", "/customer/aa/bb/cc", "something",
"/aa/bb/cc");
checkCapture("/customer/{*something}", "/customer/", "something", "/");
checkCapture("/customer/////{*something}", "/customer/", "something", "/");
checkCapture("/customer/{*something}", "/customer//////99", "something", "/99");
checkCapture("/customer///{*something}", "/customer//////99", "something", "/99");
checkCapture("/customer/////{*something}", "/customer/////", "something", "/");
checkCapture("/customer/////{*something}", "/customer//////", "something", "//");
checkCapture("/customer//////{*something}", "/customer//////99", "something", "/99");
checkCapture("/customer//////{*something}", "/customer//////99", "something", "/99");
checkCapture("/customer/{*something}", "/customer", "something", "");
checkCapture("/{*something}", "", "something", "");
checkCapture("/customer/{*something}", "/customer//////99", "something", "//////99");
}
@Test
public void multipleSelectorsInPattern() {
checkMatches("///abc", "/abc");
checkMatches("//", "/");
checkMatches("abc", "abc");
checkMatches("///abc//d/e", "/abc/d/e");
checkMatches("///abc//{def}//////xyz", "/abc/foo/xyz");
public void multipleSeparatorsInPattern() {
PathPattern pp = parse("a//b//c");
assertEquals("Literal(a) Separator(/) Separator(/) Literal(b) Separator(/) Separator(/) Literal(c)",pp.toChainString());
assertTrue(pp.matches("a//b//c"));
assertEquals("Literal(a) Separator(/) WildcardTheRest(/**)",parse("a//**").toChainString());
checkMatches("///abc", "///abc");
checkNoMatch("///abc", "/abc");
checkNoMatch("//", "/");
checkMatches("//", "//");
checkNoMatch("///abc//d/e", "/abc/d/e");
checkMatches("///abc//d/e", "///abc//d/e");
checkNoMatch("///abc//{def}//////xyz", "/abc/foo/xyz");
checkMatches("///abc//{def}//////xyz", "///abc//p//////xyz");
}
@Test
public void multipleSelectorsInPath() {
checkMatches("/abc", "////abc");
checkMatches("/", "//");
checkMatches("/abc//def///ghi", "/abc/def/ghi");
checkNoMatch("/abc", "////abc");
checkNoMatch("/", "//");
checkNoMatch("/abc/def/ghi", "/abc//def///ghi");
checkNoMatch("/abc", "////abc");
checkMatches("////abc", "////abc");
checkNoMatch("/", "//");
checkNoMatch("/abc//def///ghi", "/abc/def/ghi");
checkMatches("/abc//def///ghi", "/abc//def///ghi");
}
@Test
public void multipleSelectorsInPatternAndPath() {
checkMatches("///one///two///three", "//one/////two///////three");
checkMatches("//one//two//three", "/one/////two/three");
checkCapture("///{foo}///bar", "/one/bar", "foo", "one");
public void multipleSeparatorsInPatternAndPath() {
checkNoMatch("///one///two///three", "//one/////two///////three");
checkMatches("//one/////two///////three", "//one/////two///////three");
checkNoMatch("//one//two//three", "/one/////two/three");
checkMatches("/one/////two/three", "/one/////two/three");
checkCapture("///{foo}///bar", "///one///bar", "foo", "one");
}
@Test
public void wildcards() {
checkMatches("/*/bar", "/foo/bar");
checkNoMatch("/*/bar", "/foo/baz");
checkNoMatch("/*/bar", "//bar");
checkMatches("/f*/bar", "/foo/bar");
checkMatches("/*/bar", "/foo/bar");
checkMatches("a/*","a/");
checkMatches("/*","/");
checkMatches("/*/bar", "/foo/bar");
checkNoMatch("/*/bar", "/foo/baz");
checkMatches("/f*/bar", "/foo/bar");
@@ -183,20 +374,19 @@ public class PathPatternMatcherTests {
checkMatches("/a*b*c*d/bar", "/abcd/bar");
checkMatches("*a*", "testa");
checkMatches("a/*", "a/");
checkNoMatch("a/*", "a//"); // trailing slash, so is allowed
checkMatches("a/*", "a/a/"); // trailing slash, so is allowed
PathPatternParser ppp = new PathPatternParser();
ppp.setMatchOptionalTrailingSlash(false);
assertFalse(ppp.parse("a/*").matches("a//"));
checkMatches("a/*", "a/a");
checkNoMatch("a/*", "a/a/");
checkMatches("a/*", "a/a/"); // trailing slash is optional
checkMatches("/resource/**", "/resource");
checkNoMatch("/resource/**", "/resourceX");
checkNoMatch("/resource/**", "/resourceX/foobar");
checkMatches("/resource/**", "/resource/foobar");
}
@Test
public void trailingSeparators() {
checkNoMatch("aaa/", "aaa");
}
@Test
public void constrainedMatches() {
checkCapture("{foo:[0-9]*}", "123", "foo", "123");
@@ -246,7 +436,8 @@ public class PathPatternMatcherTests {
checkMatches("test*aaa", "testblaaaa");
checkNoMatch("test*", "tst");
checkNoMatch("test*", "tsttest");
checkNoMatch("test*", "test/");
checkMatches("test*", "test/"); // trailing slash is optional
checkMatches("test*", "test"); // trailing slash is optional
checkNoMatch("test*", "test/t");
checkNoMatch("test/*", "test");
checkNoMatch("*test*", "tsttst");
@@ -265,8 +456,6 @@ public class PathPatternMatcherTests {
checkMatches("/**", "");
checkMatches("/books/**", "/books");
checkMatches("/books////**", "/books");
checkMatches("/books////**", "/books////");
checkMatches("/**", "/testing/testing");
checkMatches("/*/**", "/testing/testing");
checkMatches("/bla*bla/test", "/blaXXXbla/test");
@@ -314,6 +503,12 @@ public class PathPatternMatcherTests {
@Test
public void matchStart() {
checkStartNoMatch("test/*/","test//");
checkStartMatches("test/*","test/abc");
checkStartMatches("test/*/def","test/abc/def");
checkStartNoMatch("test/*/def","test//");
checkStartNoMatch("test/*/def","test//def");
checkStartMatches("test/{a}_{b}/foo", "test/a_b");
checkStartMatches("test/?/abc", "test/a");
checkStartMatches("test/{*foobar}", "test/");
@@ -357,7 +552,8 @@ public class PathPatternMatcherTests {
checkStartMatches("*.*", "test.test.test");
checkStartMatches("test*aaa", "testblaaaa");
checkStartNoMatch("test*", "tst");
checkStartNoMatch("test*", "test/");
checkStartMatches("test*", "test/"); // trailing slash is optional
checkStartMatches("test*", "test");
checkStartNoMatch("test*", "tsttest");
checkStartNoMatch("test*", "test/t");
checkStartMatches("test/*", "test");
@@ -398,7 +594,7 @@ public class PathPatternMatcherTests {
"/XXXblaXXXX/testing/bla/testing/testing.jpg");
checkStartMatches("/abc/{foo}", "/abc/def");
checkStartNoMatch("/abc/{foo}", "/abc/def/");
checkStartMatches("/abc/{foo}", "/abc/def/"); // trailing slash is optional
checkStartMatches("/abc/{foo}/", "/abc/def/");
checkStartNoMatch("/abc/{foo}/", "/abc/def/ghi");
checkStartMatches("/abc/{foo}/", "/abc/def");
@@ -556,8 +752,8 @@ public class PathPatternMatcherTests {
@Test
public void extractPathWithinPattern_spr15259() {
checkExtractPathWithinPattern("/**","//","/");
checkExtractPathWithinPattern("/**","/","");
checkExtractPathWithinPattern("/**","//","");
checkExtractPathWithinPattern("/**","","");
checkExtractPathWithinPattern("/**","/foobar","foobar");
}
@@ -579,10 +775,8 @@ public class PathPatternMatcherTests {
checkExtractPathWithinPattern("/a/b/c*d*/*.html", "/a/b/cod/foo.html", "cod/foo.html");
checkExtractPathWithinPattern("a/{foo}/b/{bar}", "a/c/b/d", "c/b/d");
checkExtractPathWithinPattern("a/{foo}_{bar}/d/e", "a/b_c/d/e", "b_c/d/e");
checkExtractPathWithinPattern("aaa//*///ccc///ddd", "aaa/bbb/ccc/ddd", "bbb/ccc/ddd");
checkExtractPathWithinPattern("aaa/*/ccc/ddd", "aaa//bbb//ccc/ddd", "bbb/ccc/ddd");
checkExtractPathWithinPattern("aaa//*///ccc///ddd", "aaa//bbb///ccc///ddd", "bbb/ccc/ddd");
checkExtractPathWithinPattern("aaa//*///ccc///ddd", "aaa//bbb//ccc/ddd", "bbb/ccc/ddd");
checkExtractPathWithinPattern("aaa//*///ccc///ddd", "aaa/////bbb//ccc/ddd", "bbb/ccc/ddd");
checkExtractPathWithinPattern("aaa/c*/ddd/", "aaa/ccc///ddd///", "ccc/ddd");
checkExtractPathWithinPattern("", "", "");
checkExtractPathWithinPattern("/", "", "");
@@ -604,13 +798,18 @@ public class PathPatternMatcherTests {
pp = new PathPatternParser().parse("/{foo}/{bar}");
assertTrue(pp.matches("/abc/def"));
assertFalse(pp.matches("/def"));
assertFalse(pp.matches("/"));
assertFalse(pp.matches("//def"));
assertFalse(pp.matches("//"));
pp = parse("/{foo}/boo");
assertTrue(pp.matches("/abc/boo"));
assertTrue(pp.matches("/a/boo"));
assertFalse(pp.matches("/boo"));
assertFalse(pp.matches("//boo"));
pp = parse("/{foo}*");
assertTrue(pp.matches("/abc"));
@@ -630,11 +829,14 @@ public class PathPatternMatcherTests {
checkCapture("/{foo:[a-z][a-z]}{bar:[a-z]}", "/abc", "foo", "ab", "bar", "c");
// Only patterns not capturing variables cannot match against just /
pp = new PathPatternParser().parse("/****");
PathPatternParser ppp = new PathPatternParser();
ppp.setMatchOptionalTrailingSlash(true);
pp = ppp.parse("/****");
assertTrue(pp.matches("/abcdef"));
assertTrue(pp.matches("/"));
assertTrue(pp.matches("/"));
assertTrue(pp.matches("//"));
// Confirming AntPathMatcher behaviour:
assertFalse(new AntPathMatcher().match("/{foo}", "/"));
assertTrue(new AntPathMatcher().match("/{foo}", "/a"));
@@ -682,6 +884,7 @@ public class PathPatternMatcherTests {
checkCapture("/{bla}.*", "/testing.html", "bla", "testing");
Map<String, String> extracted = checkCapture("/abc", "/abc");
assertEquals(0, extracted.size());
checkCapture("/{bla}/foo","/a/foo");
}
@Test
@@ -990,6 +1193,7 @@ public class PathPatternMatcherTests {
private PathPattern parse(String path) {
PathPatternParser pp = new PathPatternParser();
pp.setMatchOptionalTrailingSlash(true);
return pp.parse(path);
}
@@ -998,18 +1202,21 @@ public class PathPatternMatcherTests {
private void checkMatches(String uriTemplate, String path) {
PathPatternParser parser = (separator == PathPatternParser.DEFAULT_SEPARATOR
? new PathPatternParser() : new PathPatternParser(separator));
parser.setMatchOptionalTrailingSlash(true);
PathPattern p = parser.parse(uriTemplate);
assertTrue(p.matches(path));
}
private void checkStartNoMatch(String uriTemplate, String path) {
PathPatternParser p = new PathPatternParser();
p.setMatchOptionalTrailingSlash(true);
PathPattern pattern = p.parse(uriTemplate);
assertFalse(pattern.matchStart(path));
}
private void checkStartMatches(String uriTemplate, String path) {
PathPatternParser p = new PathPatternParser();
p.setMatchOptionalTrailingSlash(true);
PathPattern pattern = p.parse(uriTemplate);
assertTrue(pattern.matchStart(path));
}

View File

@@ -41,7 +41,7 @@ public class PathPatternParserTests {
checkStructure("foo");
checkStructure("foo/");
checkStructure("/foo/");
checkStructure("//");
checkStructure("");
}
@Test
@@ -49,7 +49,7 @@ public class PathPatternParserTests {
p = checkStructure("?");
assertPathElements(p, SingleCharWildcardedPathElement.class);
checkStructure("/?/");
checkStructure("//?abc?/");
checkStructure("/?abc?/");
}
@Test
@@ -175,7 +175,7 @@ public class PathPatternParserTests {
p = checkStructure("{foo}");
assertEquals(CaptureVariablePathElement.class.getName(), p.getHeadSection().getClass().getName());
checkStructure("/{foo}");
checkStructure("//{f}/");
checkStructure("/{f}/");
checkStructure("/{foo}/{bar}/{wibble}");
}
@@ -208,13 +208,13 @@ public class PathPatternParserTests {
checkError("{", 1, PatternMessage.MISSING_CLOSE_CAPTURE);
checkError("{abc", 4, PatternMessage.MISSING_CLOSE_CAPTURE);
checkError("{/}", 1, PatternMessage.MISSING_CLOSE_CAPTURE);
checkError("//{", 3, PatternMessage.MISSING_CLOSE_CAPTURE);
checkError("/{", 2, PatternMessage.MISSING_CLOSE_CAPTURE);
checkError("}", 0, PatternMessage.MISSING_OPEN_CAPTURE);
checkError("/}", 1, PatternMessage.MISSING_OPEN_CAPTURE);
checkError("def}", 3, PatternMessage.MISSING_OPEN_CAPTURE);
checkError("//{/}", 3, PatternMessage.MISSING_CLOSE_CAPTURE);
checkError("//{{/}", 3, PatternMessage.ILLEGAL_NESTED_CAPTURE);
checkError("//{abc{/}", 6, PatternMessage.ILLEGAL_NESTED_CAPTURE);
checkError("/{/}", 2, PatternMessage.MISSING_CLOSE_CAPTURE);
checkError("/{{/}", 2, PatternMessage.ILLEGAL_NESTED_CAPTURE);
checkError("/{abc{/}", 5, PatternMessage.ILLEGAL_NESTED_CAPTURE);
checkError("/{0abc}/abc", 2, PatternMessage.ILLEGAL_CHARACTER_AT_START_OF_CAPTURE_DESCRIPTOR);
checkError("/{a?bc}/abc", 3, PatternMessage.ILLEGAL_CHARACTER_IN_CAPTURE_DESCRIPTOR);
checkError("/{abc}_{abc}", 1, PatternMessage.ILLEGAL_DOUBLE_CAPTURE);
@@ -294,15 +294,19 @@ public class PathPatternParserTests {
@Test
public void multipleSeparatorPatterns() {
p = checkStructure("///aaa");
assertEquals(4, p.getNormalizedLength());
assertPathElements(p, SeparatorPathElement.class, LiteralPathElement.class);
assertEquals(6, p.getNormalizedLength());
assertPathElements(p, SeparatorPathElement.class, SeparatorPathElement.class,
SeparatorPathElement.class, LiteralPathElement.class);
p = checkStructure("///aaa////aaa/b");
assertEquals(10, p.getNormalizedLength());
assertPathElements(p, SeparatorPathElement.class, LiteralPathElement.class,
SeparatorPathElement.class, LiteralPathElement.class, SeparatorPathElement.class, LiteralPathElement.class);
assertEquals(15, p.getNormalizedLength());
assertPathElements(p, SeparatorPathElement.class, SeparatorPathElement.class,
SeparatorPathElement.class, LiteralPathElement.class, SeparatorPathElement.class,
SeparatorPathElement.class, SeparatorPathElement.class, SeparatorPathElement.class,
LiteralPathElement.class, SeparatorPathElement.class, LiteralPathElement.class);
p = checkStructure("/////**");
assertEquals(1, p.getNormalizedLength());
assertPathElements(p, WildcardTheRestPathElement.class);
assertEquals(5, p.getNormalizedLength());
assertPathElements(p, SeparatorPathElement.class, SeparatorPathElement.class,
SeparatorPathElement.class, SeparatorPathElement.class, WildcardTheRestPathElement.class);
}
@Test