From 9caa352470eb47055332755263698a695b269c79 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 23 Jan 2014 14:09:35 +0100 Subject: [PATCH] #137 - Improved TemplateVariables to combine variables. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Request parameters variables {?…} and continued ones {&…} are now combined by TemplateVariables. Improved detection of the base URI and adapted the rendering logic accordingly. UriTemplates can now be augmented with additional TemplateVariables. Added equals(…) and hashCode() methods for TempalteVariables. --- .../hateoas/TemplateVariable.java | 13 +++++- .../hateoas/TemplateVariables.java | 31 +++++++++++++- .../springframework/hateoas/UriTemplate.java | 42 ++++++++++++++++--- .../hateoas/TemplateVariablesUnitTest.java | 28 +++++++++++++ .../hateoas/UriTemplateUnitTest.java | 40 ++++++++++++++++-- 5 files changed, 142 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/TemplateVariable.java b/src/main/java/org/springframework/hateoas/TemplateVariable.java index c611927a..7074053c 100644 --- a/src/main/java/org/springframework/hateoas/TemplateVariable.java +++ b/src/main/java/org/springframework/hateoas/TemplateVariable.java @@ -15,6 +15,9 @@ */ package org.springframework.hateoas; +import java.util.Arrays; +import java.util.List; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -109,8 +112,8 @@ public final class TemplateVariable { * @param variable must not be {@literal null}. * @return */ - boolean isOfSameTypeAs(TemplateVariable variable) { - return this.type.equals(variable.type); + boolean isCombinable(TemplateVariable variable) { + return this.type.canBeCombinedWith(variable.type); } /* @@ -171,6 +174,8 @@ public final class TemplateVariable { SEGMENT("/", true), // FRAGMENT("#", true); + private static final List combinableTypes = Arrays.asList(REQUEST_PARAM, REQUEST_PARAM_CONTINUED); + private final String key; private final boolean optional; @@ -188,6 +193,10 @@ public final class TemplateVariable { return optional; } + public boolean canBeCombinedWith(VariableType type) { + return this.equals(type) || combinableTypes.contains(this) && combinableTypes.contains(type); + } + /** * Returns the {@link VariableType} for the given variable key. * diff --git a/src/main/java/org/springframework/hateoas/TemplateVariables.java b/src/main/java/org/springframework/hateoas/TemplateVariables.java index fbd32937..fc1be367 100644 --- a/src/main/java/org/springframework/hateoas/TemplateVariables.java +++ b/src/main/java/org/springframework/hateoas/TemplateVariables.java @@ -125,7 +125,7 @@ public final class TemplateVariables implements Iterable { if (previous == null) { builder.append("{").append(variable.getType().toString()); - } else if (!previous.isOfSameTypeAs(variable)) { + } else if (!previous.isCombinable(variable)) { builder.append("}{").append(variable.getType().toString()); } else { builder.append(","); @@ -137,4 +137,33 @@ public final class TemplateVariables implements Iterable { return builder.append("}").toString(); } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof TemplateVariables)) { + return false; + } + + TemplateVariables that = (TemplateVariables) obj; + + return this.variables.equals(that.variables); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return this.variables.hashCode(); + } } diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index ff3216d3..1d6bfa34 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -60,15 +60,17 @@ public class UriTemplate implements Iterable { int start = matcher.start(0); - if (start < baseUriEndIndex) { - baseUriEndIndex = start; - } - VariableType type = VariableType.from(matcher.group(1)); String[] names = matcher.group(2).split(","); for (String name : names) { - variables.add(new TemplateVariable(name, type)); + TemplateVariable variable = new TemplateVariable(name, type); + + if (!variable.isRequired() && start < baseUriEndIndex) { + baseUriEndIndex = start; + } + + variables.add(variable); } } @@ -90,6 +92,21 @@ public class UriTemplate implements Iterable { this.variables = variables == null ? TemplateVariables.NONE : variables; } + /** + * Creates a new {@link UriTemplate} with the current {@link TemplateVariable}s augmented with the given ones. + * + * @param variables can be {@literal null}. + * @return + */ + public UriTemplate with(TemplateVariables variables) { + + if (variables == null) { + return this; + } + + return new UriTemplate(baseUri, this.variables.concat(variables)); + } + /** * Returns whether the given candidate is a URI template. * @@ -193,7 +210,20 @@ public class UriTemplate implements Iterable { */ @Override public String toString() { - return baseUri + variables.toString(); + return baseUri + getOptionalVariables().toString(); + } + + private TemplateVariables getOptionalVariables() { + + List result = new ArrayList(); + + for (TemplateVariable variable : this) { + if (!variable.isRequired()) { + result.add(variable); + } + } + + return new TemplateVariables(result); } /** diff --git a/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTest.java b/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTest.java index d1f8a49e..2bc06c67 100644 --- a/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTest.java +++ b/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTest.java @@ -88,4 +88,32 @@ public class TemplateVariablesUnitTest { assertThat(variables.toString(), is("{/foo}{?bar}")); } + /** + * @see #137 + */ + @Test + public void combinesContinuedParamWithParam() { + + TemplateVariable first = new TemplateVariable("foo", VariableType.REQUEST_PARAM); + TemplateVariable second = new TemplateVariable("bar", VariableType.REQUEST_PARAM_CONTINUED); + + TemplateVariables variables = new TemplateVariables(first, second); + + assertThat(variables.toString(), is("{?foo,bar}")); + } + + /** + * @see #137 + */ + @Test + public void combinesContinuedParameterWithParameter() { + + TemplateVariable first = new TemplateVariable("foo", VariableType.REQUEST_PARAM_CONTINUED); + TemplateVariable second = new TemplateVariable("bar", VariableType.REQUEST_PARAM); + + TemplateVariables variables = new TemplateVariables(first, second); + + assertThat(variables.toString(), is("{&foo,bar}")); + } + } diff --git a/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java b/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java index c205c67d..b7d84863 100644 --- a/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java +++ b/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java @@ -19,8 +19,12 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; @@ -177,10 +181,40 @@ public class UriTemplateUnitTest { assertThat(new UriTemplate("http://localhost:8080/foo{?bar}").expand().toString(), is("http://localhost:8080/foo")); } - private static void assertVariables(UriTemplate template, TemplateVariable... variables) { + /** + * @see #137 + */ + @Test + public void rendersUriTempalteWithPathVariable() { - assertThat(template.getVariableNames(), hasSize(variables.length)); - assertThat(template.getVariables(), hasSize(variables.length)); + UriTemplate template = new UriTemplate("/{foo}/bar{?page}"); + assertThat(template.toString(), is("/{foo}/bar{?page}")); + } + + /** + * #@see 137 + */ + @Test + public void addsTemplateVariables() { + + UriTemplate source = new UriTemplate("/{foo}/bar{?page}"); + List toAdd = Arrays.asList(new TemplateVariable("bar", VariableType.REQUEST_PARAM)); + + List expected = new ArrayList(); + expected.addAll(source.getVariables()); + expected.addAll(toAdd); + + assertVariables(source.with(new TemplateVariables(toAdd)), expected); + } + + private static void assertVariables(UriTemplate template, TemplateVariable... variables) { + assertVariables(template, Arrays.asList(variables)); + } + + private static void assertVariables(UriTemplate template, Collection variables) { + + assertThat(template.getVariableNames(), hasSize(variables.size())); + assertThat(template.getVariables(), hasSize(variables.size())); for (TemplateVariable variable : variables) {