Skip to content

Commit fb9e184

Browse files
ryanpbrewsterandreamlin
authored andcommitted
Modify PathTemplate so that double-star (PATH_WILDCARD) matches 0+ segments (#75)
1 parent bd24e0a commit fb9e184

File tree

3 files changed

+63
-26
lines changed

3 files changed

+63
-26
lines changed

sdk-platform-java/api-common-java/build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ targetCompatibility = 1.7
3636
// ------------
3737

3838
ext {
39-
4039
// Shortcuts for libraries we are using
4140
libraries = [
41+
javax_annotations: 'javax.annotation:javax.annotation-api:1.3.1',
4242
guava: 'com.google.guava:guava:26.0-android',
4343
jsr305: 'com.google.code.findbugs:jsr305:3.0.2',
4444
autovalue: 'com.google.auto.value:auto-value:1.1',
@@ -62,7 +62,8 @@ repositories {
6262

6363
dependencies {
6464
compile libraries.guava,
65-
libraries.jsr305
65+
libraries.jsr305,
66+
libraries.javax_annotations
6667

6768
compileOnly libraries.autovalue,
6869
libraries.error_prone_annotations

sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,8 @@ private boolean match(
612612
// This is the final segment, and this check should have already been performed by the
613613
// caller. The matching value is no longer present in the input.
614614
break;
615-
default:
615+
case LITERAL:
616+
case WILDCARD:
616617
if (inPos >= input.size()) {
617618
// End of input
618619
return false;
@@ -627,37 +628,41 @@ private boolean match(
627628
}
628629
if (currentVar != null) {
629630
// Create or extend current match
630-
String current = values.get(currentVar);
631-
if (current == null) {
632-
values.put(currentVar, next);
633-
} else {
634-
values.put(currentVar, current + "/" + next);
635-
}
631+
values.put(currentVar, concatCaptures(values.get(currentVar), next));
636632
}
637-
if (seg.kind() == SegmentKind.PATH_WILDCARD) {
638-
// Compute the number of additional input the ** can consume. This
639-
// is possible because we restrict patterns to have only one **.
640-
int segsToMatch = 0;
641-
for (int i = segPos; i < segments.size(); i++) {
642-
switch (segments.get(i).kind()) {
643-
case BINDING:
644-
case END_BINDING:
645-
// skip
646-
continue;
647-
default:
648-
segsToMatch++;
649-
}
650-
}
651-
int available = (input.size() - inPos) - segsToMatch;
652-
while (available-- > 0) {
653-
values.put(currentVar, values.get(currentVar) + "/" + decodeUrl(input.get(inPos++)));
633+
break;
634+
case PATH_WILDCARD:
635+
// Compute the number of additional input the ** can consume. This
636+
// is possible because we restrict patterns to have only one **.
637+
int segsToMatch = 0;
638+
for (int i = segPos; i < segments.size(); i++) {
639+
switch (segments.get(i).kind()) {
640+
case BINDING:
641+
case END_BINDING:
642+
// skip
643+
continue;
644+
default:
645+
segsToMatch++;
654646
}
655647
}
648+
int available = (input.size() - inPos) - segsToMatch;
649+
// If this segment is empty, make sure it is still captured.
650+
if (available == 0 && !values.containsKey(currentVar)) {
651+
values.put(currentVar, "");
652+
}
653+
while (available-- > 0) {
654+
values.put(
655+
currentVar, concatCaptures(values.get(currentVar), decodeUrl(input.get(inPos++))));
656+
}
656657
}
657658
}
658659
return inPos == input.size();
659660
}
660661

662+
private static String concatCaptures(@Nullable String cur, String next) {
663+
return cur == null ? next : cur + "/" + next;
664+
}
665+
661666
// Template Instantiation
662667
// ======================
663668

sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,37 @@ public void matchWithHostNameAndProtocolWithTemplateStartWithBinding() {
107107
Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c");
108108
}
109109

110+
@Test
111+
public void pathWildcards_matchZeroOrMoreSegments() {
112+
PathTemplate start = PathTemplate.create("{glob=**}/b");
113+
PathTemplate middle = PathTemplate.create("a/{glob=**}/b");
114+
PathTemplate end = PathTemplate.create("a/{glob=**}");
115+
116+
Truth.assertThat(start.match("b").get("glob")).isEmpty();
117+
Truth.assertThat(start.match("/b").get("glob")).isEmpty();
118+
Truth.assertThat(start.match("a/b").get("glob")).isEqualTo("a");
119+
Truth.assertThat(start.match("a/a/a/b").get("glob")).isEqualTo("a/a/a");
120+
121+
Truth.assertThat(middle.match("a/b").get("glob")).isEmpty();
122+
Truth.assertThat(middle.match("a//b").get("glob")).isEmpty();
123+
Truth.assertThat(middle.match("a/x/b").get("glob")).isEqualTo("x");
124+
Truth.assertThat(middle.match("a/x/y/z/b").get("glob")).isEqualTo("x/y/z");
125+
126+
Truth.assertThat(end.match("a").get("glob")).isEmpty();
127+
Truth.assertThat(end.match("a/").get("glob")).isEmpty();
128+
Truth.assertThat(end.match("a/b").get("glob")).isEqualTo("b");
129+
Truth.assertThat(end.match("a/b/b/b").get("glob")).isEqualTo("b/b/b");
130+
}
131+
132+
@Test
133+
public void pathWildcard_canMatchTheEmptyString() {
134+
PathTemplate template = PathTemplate.create("{glob=**}");
135+
136+
Truth.assertThat(template.match("").get("glob")).isEmpty();
137+
Truth.assertThat(template.match("a").get("glob")).isEqualTo("a");
138+
Truth.assertThat(template.match("a/b").get("glob")).isEqualTo("a/b");
139+
}
140+
110141
@Test
111142
public void matchWithCustomMethod() {
112143
PathTemplate template = PathTemplate.create("buckets/*/objects/*:custom");

0 commit comments

Comments
 (0)