diff --git a/src/main/java/org/springframework/hateoas/server/core/AnnotationMappingDiscoverer.java b/src/main/java/org/springframework/hateoas/server/core/AnnotationMappingDiscoverer.java index e6dfc9b3..481bc3ed 100644 --- a/src/main/java/org/springframework/hateoas/server/core/AnnotationMappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/server/core/AnnotationMappingDiscoverer.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.http.HttpMethod; @@ -42,7 +41,6 @@ import org.springframework.web.bind.annotation.RequestMethod; public class AnnotationMappingDiscoverer implements MappingDiscoverer { private static final Pattern MULTIPLE_SLASHES = Pattern.compile("/{2,}"); - private static final Pattern TEMPLATE_VARIABLE_NAME = Pattern.compile("\\{(.*)\\}"); private final Class annotationType; private final String mappingAttributeName; @@ -205,23 +203,44 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer { return MULTIPLE_SLASHES.matcher(result.toString()).replaceAll("/"); } - private static String cleanupPart(String variable) { + private static String cleanupPart(String part) { - if (!variable.contains("{")) { - return variable; + StringBuilder builder = new StringBuilder(); + int level = 0; + boolean inRegex = false; + + for (int i = 0; i < part.length(); i++) { + + char character = part.charAt(i); + + if (character == '{') { + + level++; + + if (level == 1) { + builder.append(character); + continue; + } + } + + if (level == 1 && character == ':') { + inRegex = true; + } + + if (character == '}') { + + level--; + + if (level == 0) { + inRegex = false; + } + } + + if (!inRegex) { + builder.append(character); + } } - Matcher matcher = TEMPLATE_VARIABLE_NAME.matcher(variable); - - if (!matcher.find()) { - return variable; - } - - String rawName = matcher.group(1); - int colonIndex = rawName.indexOf(':'); - - return colonIndex < 0 - ? variable - : variable.replace(matcher.group(0), "{" + rawName.substring(0, colonIndex) + "}"); + return builder.toString(); } } diff --git a/src/test/java/org/springframework/hateoas/server/core/AnnotationMappingDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/server/core/AnnotationMappingDiscovererUnitTest.java index ce4fc59c..9702ab58 100755 --- a/src/test/java/org/springframework/hateoas/server/core/AnnotationMappingDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/server/core/AnnotationMappingDiscovererUnitTest.java @@ -160,6 +160,14 @@ class AnnotationMappingDiscovererUnitTest { assertThat(discoverer.getMapping(method)).isEqualTo("/type/foo/{bar}"); } + @Test // #1455 + void extractsMultipleRegularExpressionVariablesCorrectly() throws Exception { + + Method method = MyController.class.getMethod("multipleRegularExpressions"); + + assertThat(discoverer.getMapping(method)).isEqualTo("/type/spring-web/{symbolicName}-{version}{extension}"); + } + @RequestMapping("/type") interface MyController { @@ -174,6 +182,9 @@ class AnnotationMappingDiscovererUnitTest { @RequestMapping("/foo/{bar:[ABC]{1}}") void mappingWithMatchingExpression(); + + @GetMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}") + void multipleRegularExpressions(); } interface ControllerWithoutTypeLevelMapping {