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) {