diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index f231245d..40447bfe 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -135,6 +135,17 @@ public class Link implements Serializable { return getUriTemplate().getVariableNames(); } + /** + * Returns all {@link TemplateVariables} contained in the {@link Link}. + * + * @return + */ + @org.codehaus.jackson.annotate.JsonIgnore + @JsonIgnore + public List getVariables() { + return getUriTemplate().getVariables(); + } + /** * Returns whether the link is templated. * diff --git a/src/main/java/org/springframework/hateoas/TemplateVariable.java b/src/main/java/org/springframework/hateoas/TemplateVariable.java new file mode 100644 index 00000000..c611927a --- /dev/null +++ b/src/main/java/org/springframework/hateoas/TemplateVariable.java @@ -0,0 +1,217 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A single template variable. + * + * @author Oliver Gierke + */ +public final class TemplateVariable { + + private final String name; + private final TemplateVariable.VariableType type; + private final String description; + + /** + * Creates a new {@link TemplateVariable} with the given name and type. + * + * @param name must not be {@literal null} or empty. + * @param type must not be {@literal null}. + */ + public TemplateVariable(String name, TemplateVariable.VariableType type) { + this(name, type, ""); + } + + /** + * Creates a new {@link TemplateVariable} with the given name, type and description. + * + * @param name must not be {@literal null} or empty. + * @param type must not be {@literal null}. + * @param description must not be {@literal null}. + */ + public TemplateVariable(String name, TemplateVariable.VariableType type, String description) { + + Assert.hasText("Variable name must not be null or empty!"); + Assert.notNull("Variable type must not be null!"); + Assert.notNull("Description must not be null!"); + + this.name = name; + this.type = type; + this.description = description; + } + + /** + * Returns the name of the variable. + * + * @return + */ + public String getName() { + return this.name; + } + + /** + * Returns the type of the variable. + * + * @return the type + */ + public VariableType getType() { + return type; + } + + /** + * Returns the description of the variable. + * + * @return + */ + public String getDescription() { + return description; + } + + /** + * Returns whether the variable has a description. + * + * @return + */ + public boolean hasDescription() { + return StringUtils.hasText(description); + } + + /** + * Returns whether the template variable is optional, which means the template can be expanded to a URI without a + * value given for that variable. + * + * @return + */ + boolean isRequired() { + return !type.isOptional(); + } + + /** + * Returns whether the given {@link TemplateVariable} is of the same type as the current one. + * + * @param variable must not be {@literal null}. + * @return + */ + boolean isOfSameTypeAs(TemplateVariable variable) { + return this.type.equals(variable.type); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + String base = String.format("{%s%s}", type.toString(), name); + return StringUtils.hasText(description) ? String.format("%s - %s", base, description) : base; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + + if (obj == this) { + return true; + } + + if (!(obj instanceof TemplateVariable)) { + return false; + } + + TemplateVariable that = (TemplateVariable) obj; + return this.name.equals(that.name) && this.type.equals(that.type); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + + int result = 17; + + result += this.name.hashCode(); + result += this.type.hashCode(); + + return result; + } + + /** + * An enumeration for all supported variable types. + * + * @author Oliver Gierke + */ + public static enum VariableType { + + PATH_VARIABLE("", false), // + REQUEST_PARAM("?", true), // + REQUEST_PARAM_CONTINUED("&", true), // + SEGMENT("/", true), // + FRAGMENT("#", true); + + private final String key; + private final boolean optional; + + private VariableType(String key, boolean optional) { + this.key = key; + this.optional = optional; + } + + /** + * Returns whether the variable of this type is optional. + * + * @return + */ + public boolean isOptional() { + return optional; + } + + /** + * Returns the {@link VariableType} for the given variable key. + * + * @param key must not be {@literal null}. + * @return + */ + public static TemplateVariable.VariableType from(String key) { + + for (TemplateVariable.VariableType type : values()) { + if (type.key.equals(key)) { + return type; + } + } + + throw new IllegalArgumentException("Unsupported variable type " + key + "!"); + } + + /* + * (non-Javadoc) + * @see java.lang.Enum#toString() + */ + @Override + public String toString() { + return key; + } + } +} diff --git a/src/main/java/org/springframework/hateoas/TemplateVariables.java b/src/main/java/org/springframework/hateoas/TemplateVariables.java new file mode 100644 index 00000000..a481882d --- /dev/null +++ b/src/main/java/org/springframework/hateoas/TemplateVariables.java @@ -0,0 +1,131 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Wrapper type for a collection of {@link TemplateVariable}. + * + * @author Oliver Gierke + */ +public class TemplateVariables implements Iterable { + + public static TemplateVariables NONE = new TemplateVariables(Collections. emptyList()); + + private final List variables; + + /** + * Creates a new {@link TemplateVariables} for the given {@link TemplateVariable}s. + * + * @param variables must not be {@literal null}. + */ + public TemplateVariables(TemplateVariable... variables) { + this(Arrays.asList(variables)); + } + + /** + * Creates a new {@link TemplateVariables} for the given {@link TemplateVariable}s. + * + * @param variables must not be {@literal null}. + */ + public TemplateVariables(List variables) { + + Assert.notNull(variables, "Template variables must not be null!"); + this.variables = Collections.unmodifiableList(variables); + } + + /** + * Concatenates the given {@link TemplateVariable}s to the current one. + * + * @param variables must not be {@literal null}. + * @return + */ + public TemplateVariables concat(TemplateVariable... variables) { + return concat(Arrays.asList(variables)); + } + + /** + * Concatenates the given {@link TemplateVariable}s to the current one. + * + * @param variables must not be {@literal null}. + * @return + */ + public TemplateVariables concat(Collection variables) { + + List result = new ArrayList(this.variables.size() + variables.size()); + result.addAll(this.variables); + result.addAll(variables); + + return new TemplateVariables(result); + } + + /** + * Concatenates the given {@link TemplateVariables} to the current one. + * + * @param variables must not be {@literal null}. + * @return + */ + public TemplateVariables concat(TemplateVariables variables) { + return concat(variables.variables); + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return this.variables.iterator(); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + + if (variables.isEmpty()) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + TemplateVariable previous = null; + + for (TemplateVariable variable : variables) { + + if (previous == null) { + builder.append("{").append(variable.getType().toString()); + } else if (!previous.isOfSameTypeAs(variable)) { + builder.append("}{").append(variable.getType().toString()); + } else { + builder.append(","); + } + + previous = variable; + builder.append(variable.getName()); + } + + return builder.append("}").toString(); + } +} diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index 16d2a455..cced35b0 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.springframework.hateoas.UriTemplate.TemplateVariable; +import org.springframework.hateoas.TemplateVariable.VariableType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; @@ -147,7 +147,7 @@ public class UriTemplate implements Iterable { UriComponentsBuilder builder = UriComponentsBuilder.fromPath(baseUri); for (TemplateVariable variable : variables) { - appendToBuilder(builder, variable, parameters.get(variable.name)); + appendToBuilder(builder, variable, parameters.get(variable.getName())); } return builder.build().toUri(); @@ -175,16 +175,16 @@ public class UriTemplate implements Iterable { if (variable.isRequired()) { throw new IllegalArgumentException(String.format("Template variable %s is required but no value was given!", - variable.name)); + variable.getName())); } return; } - switch (variable.type) { + switch (variable.getType()) { case REQUEST_PARAM: case REQUEST_PARAM_CONTINUED: - builder.queryParam(variable.name, value); + builder.queryParam(variable.getName(), value); break; case PATH_VARIABLE: case SEGMENT: @@ -195,126 +195,4 @@ public class UriTemplate implements Iterable { break; } } - - public static final class TemplateVariable { - - private final String name; - private final VariableType type; - - /** - * Creates a new {@link TemplateVariable} with the given name and type. - * - * @param name must not be {@literal null} or empty. - * @param type must not be {@literal null}. - */ - TemplateVariable(String name, VariableType type) { - - Assert.hasText("Variable name must not be null or empty!"); - Assert.notNull("Variable type must not be null!"); - - this.name = name; - this.type = type; - } - - /** - * Returns the name of the variable. - * - * @return - */ - public String getName() { - return this.name; - } - - /** - * Returns whether the template variable is optional, which means the template can be expanded to a URI without a - * value given for that variable. - * - * @return - */ - boolean isRequired() { - return !type.isOptional(); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - - if (obj == this) { - return true; - } - - if (!(obj instanceof TemplateVariable)) { - return false; - } - - TemplateVariable that = (TemplateVariable) obj; - return this.name.equals(that.name) && this.type.equals(that.type); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - - int result = 17; - - result += this.name.hashCode(); - result += this.type.hashCode(); - - return result; - } - } - - /** - * An enumeration for all supported variable types. - * - * @author Oliver Gierke - */ - static enum VariableType { - - PATH_VARIABLE("", false), // - REQUEST_PARAM("?", true), // - REQUEST_PARAM_CONTINUED("&", true), // - SEGMENT("/", true), // - FRAGMENT("#", true); - - private final String key; - private final boolean optional; - - private VariableType(String key, boolean optional) { - this.key = key; - this.optional = optional; - } - - /** - * Returns whether the variable of this type is optional. - * - * @return - */ - public boolean isOptional() { - return optional; - } - - /** - * Returns the {@link VariableType} for the given variable key. - * - * @param key must not be {@literal null}. - * @return - */ - public static VariableType from(String key) { - - for (VariableType type : values()) { - if (type.key.equals(key)) { - return type; - } - } - - throw new IllegalArgumentException("Unsupported variable type " + key + "!"); - } - } } diff --git a/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTests.java b/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTests.java new file mode 100644 index 00000000..df5161f8 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/TemplateVariablesUnitTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.hateoas.TemplateVariable.VariableType; + +/** + * Unit tests for {@link TemplateVariables}. + * + * @author Oliver Gierke + */ +public class TemplateVariablesUnitTests { + + /** + * @see #137 + */ + @Test + public void rendersNoTempalteVariablesAsEmptyString() { + assertThat(TemplateVariables.NONE.toString(), is("")); + } + + /** + * @see #137 + */ + @Test + public void rendersSingleVariableCorrectly() { + + TemplateVariables variables = new TemplateVariables(new TemplateVariable("foo", VariableType.SEGMENT)); + assertThat(variables.toString(), is("{/foo}")); + } + + /** + * @see #137 + */ + @Test + public void combinesMultipleVariablesOfTheSameType() { + + TemplateVariable first = new TemplateVariable("foo", VariableType.REQUEST_PARAM); + TemplateVariable second = new TemplateVariable("bar", VariableType.REQUEST_PARAM); + + TemplateVariables variables = new TemplateVariables(first, second); + + assertThat(variables.toString(), is("{?foo,bar}")); + } + + /** + * @see #137 + */ + @Test + public void combinesMultipleVariablesOfTheDifferentType() { + + TemplateVariable first = new TemplateVariable("foo", VariableType.SEGMENT); + TemplateVariable second = new TemplateVariable("bar", VariableType.REQUEST_PARAM); + + TemplateVariables variables = new TemplateVariables(first, second); + + assertThat(variables.toString(), is("{/foo}{?bar}")); + } + + /** + * @see #137 + */ + @Test + public void concatsVariables() { + + TemplateVariables first = new TemplateVariables(new TemplateVariable("foo", VariableType.SEGMENT)); + TemplateVariables second = new TemplateVariables(new TemplateVariable("bar", VariableType.REQUEST_PARAM)); + + TemplateVariables variables = first.concat(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 247f5042..ba1776e3 100644 --- a/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java +++ b/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java @@ -24,8 +24,7 @@ import java.util.HashMap; import java.util.Map; import org.junit.Test; -import org.springframework.hateoas.UriTemplate.TemplateVariable; -import org.springframework.hateoas.UriTemplate.VariableType; +import org.springframework.hateoas.TemplateVariable.VariableType; /** * Unit tests for {@link UriTemplate}.