diff --git a/src/main/java/org/springframework/hateoas/AffordanceModel.java b/src/main/java/org/springframework/hateoas/AffordanceModel.java index 35c86350..1c9e903f 100644 --- a/src/main/java/org/springframework/hateoas/AffordanceModel.java +++ b/src/main/java/org/springframework/hateoas/AffordanceModel.java @@ -204,15 +204,6 @@ public abstract class AffordanceModel { */ Stream stream(); - /** - * @deprecated since 1.4, for removal in 1.5. Prefer {@link #stream()} and selecting individual - * {@code PropertyMetadata} instances yourself. - */ - @Deprecated - default Optional getPropertyMetadata(String name) { - return stream().filter(it -> it.hasName(name)).findFirst(); - } - @Nullable default Class getType() { return null; @@ -235,23 +226,6 @@ public abstract class AffordanceModel { : DelegatingInputPayloadMetadata.of(metadata); } - /** - * Applies the {@link InputPayloadMetadata} to the given target. - * - * @param - * @param target - * @return - * @deprecated since 1.3, prefer setting up the model types via - * {@link AffordanceModel#createProperties(BiFunction)}. - */ - @Deprecated - default & Named> T applyTo(T target) { - - return getPropertyMetadata(target.getName()) // - .map(it -> target.apply(it)) // - .orElse(target); - } - T customize(T target, Function customizer); /** @@ -321,15 +295,6 @@ public abstract class AffordanceModel { return metadata.stream(); } - /* - * (non-Javadoc) - * @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#customize(org.springframework.hateoas.AffordanceModel.PropertyMetadataConfigured) - */ - @Override - public & Named> T applyTo(T target) { - return target; - } - /* * (non-Javadoc) * @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#customize(org.springframework.hateoas.AffordanceModel.Named, java.util.function.Function) diff --git a/src/main/java/org/springframework/hateoas/TemplateVariable.java b/src/main/java/org/springframework/hateoas/TemplateVariable.java index 47b5e1d3..d51a9e7c 100644 --- a/src/main/java/org/springframework/hateoas/TemplateVariable.java +++ b/src/main/java/org/springframework/hateoas/TemplateVariable.java @@ -95,18 +95,29 @@ public final class TemplateVariable implements Serializable, UriTemplate.Expanda * Static helper to fashion {@link VariableType#PATH_VARIABLE} variables. * * @param variable must not be {@literal null} or empty. - * @return + * @return will never be {@literal null}. * @since 1.1 */ public static TemplateVariable pathVariable(String variable) { - return new TemplateVariable(variable, VariableType.PATH_VARIABLE); + return new TemplateVariable(variable, VariableType.SIMPLE); + } + + /** + * Alias of {@link #pathVariable(String)}. + * + * @param variable must not be {@literal null} or empty. + * @return will never be {@literal null}. + * @since 3.0 + */ + public static TemplateVariable simple(String variable) { + return new TemplateVariable(variable, VariableType.SIMPLE); } /** * Static helper to fashion {@link VariableType#REQUEST_PARAM} variables. * * @param parameter must not be {@literal null} or empty. - * @return + * @return will never be {@literal null}. * @since 1.1 */ public static TemplateVariable requestParameter(String parameter) { @@ -117,7 +128,7 @@ public final class TemplateVariable implements Serializable, UriTemplate.Expanda * Static helper to fashion {@link VariableType#REQUEST_PARAM_CONTINUED} variables. * * @param parameter must not be {@literal null} or empty. - * @return + * @return will never be {@literal null}. * @since 1.1 */ public static TemplateVariable requestParameterContinued(String parameter) { @@ -128,41 +139,35 @@ public final class TemplateVariable implements Serializable, UriTemplate.Expanda * Static helper to fashion {@link VariableType#SEGMENT} variables. * * @param segment must not be {@literal null} or empty. - * @return + * @return will never be {@literal null}. * @since 1.1 */ public static TemplateVariable segment(String segment) { - return new TemplateVariable(segment, VariableType.SEGMENT); + return new TemplateVariable(segment, VariableType.PATH_SEGMENT); } /** * Static helper to fashion {@link VariableType#FRAGMENT} variables. * * @param name must not be {@literal null} or empty. - * @return + * @return will never be {@literal null}. * @since 1.1 */ public static TemplateVariable fragment(String name) { return new TemplateVariable(name, VariableType.FRAGMENT); } + /** + * Static helper to fashion {@link VariableType#RESERVED_STRING} variables. + * + * @param name must not be {@literal null} or empty. + * @return will never be {@literal null}. + * @since 1.1 + */ public static TemplateVariable reservedString(String name) { return new TemplateVariable(name, VariableType.RESERVED_STRING); } - /** - * Static helper to fashion {@link VariableType#COMPOSITE_PARAM} variables. - * - * @param parameter must not be {@literal null} or empty. - * @return - * @since 1.1 - * @deprecated since 1.4, use actual parameter type and call {@link #composite()} on the instance instead. - */ - @Deprecated - public static TemplateVariable compositeParameter(String parameter) { - return new TemplateVariable(parameter, VariableType.COMPOSITE_PARAM); - } - /** * Marks the current template variable as composite value. * @@ -220,18 +225,6 @@ public final class TemplateVariable implements Serializable, UriTemplate.Expanda 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 - * @deprecated since 1.4. No replacement as template variables are never required actually. - */ - @Deprecated - boolean isRequired() { - return !type.isOptional(); - } - /** * Returns whether the given {@link TemplateVariable} is of the same type as the current one. * @@ -285,7 +278,6 @@ public final class TemplateVariable implements Serializable, UriTemplate.Expanda } public String asString() { - return "{" + type.toString() + essence() + "}"; } @@ -474,32 +466,13 @@ public final class TemplateVariable implements Serializable, UriTemplate.Expanda public enum VariableType { SIMPLE("", ",", false), // - - /** - * @deprecated since 1.4, use {@link #SIMPLE} instead. - */ - @Deprecated - PATH_VARIABLE("", ",", true), // RESERVED_STRING("+", ",", true), // DOT(".", ".", true), // REQUEST_PARAM("?", "&", true), // REQUEST_PARAM_CONTINUED("&", "&", true), // - PATH_SEGMENT("/", "/", true), // - - /** - * @deprecated since 1.4, use {@link #PATH_SEGMENT} instead. - */ - @Deprecated - SEGMENT("/", "/", true), // PATH_STYLE_PARAMETER(";", ";", true), // - FRAGMENT("#", ",", true), // - - /** - * @deprecated since 1.4. Use the actual type and call {@link TemplateVariable#composite()}. - */ - @Deprecated - COMPOSITE_PARAM("*", "", true); + FRAGMENT("#", ",", true); private static final EnumSet COMBINABLE_TYPES = EnumSet.of(REQUEST_PARAM, REQUEST_PARAM_CONTINUED); static final String DEFAULT_SEPARATOR = ","; @@ -518,7 +491,6 @@ public final class TemplateVariable implements Serializable, UriTemplate.Expanda switch (this) { case DOT: - case SEGMENT: case PATH_SEGMENT: case PATH_STYLE_PARAMETER: case REQUEST_PARAM: diff --git a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java index 338afe56..e63a2217 100644 --- a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java +++ b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java @@ -114,16 +114,6 @@ public @interface EnableHypermediaSupport { return this.mediaTypes; } - /** - * @deprecated since 2.5, in favor of {@link #getMediaTypes()} as a logical media type might have been associated - * with multiple media type identifiers (see {@link #HAL}). - * @return will never be {@literal null}. - */ - @Deprecated(since = "2.5", forRemoval = true) - public MediaType getMediaType() { - return this.mediaTypes.get(0); - } - public String getLocalPackageName() { return localPackageName; } 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 fb5c3d9e..a0a7f8bc 100644 --- a/src/main/java/org/springframework/hateoas/server/core/AnnotationMappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/server/core/AnnotationMappingDiscoverer.java @@ -42,7 +42,7 @@ import org.springframework.web.bind.annotation.RequestMethod; * @author Greg Turnquist * @author Réda Housni Alaoui */ -public class AnnotationMappingDiscoverer implements MappingDiscoverer { +public class AnnotationMappingDiscoverer implements MappingDiscoverer, RawMappingDiscoverer { private final Class annotationType; private final @Nullable String mappingAttributeName; @@ -73,50 +73,59 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer { /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.Class) */ @Override @Nullable - public String getMapping(Class type) { - - Assert.notNull(type, "Type must not be null!"); - - String[] mapping = getMappingFrom(findMergedAnnotation(type, annotationType)); - - return mapping.length == 0 ? null : mapping[0]; + public UriMapping getUriMapping(Class type) { + return UriMapping.of(getMapping(type)); } /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.reflect.Method) */ @Override @Nullable - public String getMapping(Method method) { + public UriMapping getUriMapping(Method method) { Assert.notNull(method, "Method must not be null!"); - return getMapping(method.getDeclaringClass(), method); + + return getUriMapping(method.getDeclaringClass(), method); } /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.Class, java.lang.reflect.Method) */ @Override @Nullable - public String getMapping(Class type, Method method) { + public UriMapping getUriMapping(Class type, Method method) { - Assert.notNull(type, "Type must not be null!"); - Assert.notNull(method, "Method must not be null!"); + var mapping = getMapping(type, method); - String[] mapping = getMappingFrom(findMergedAnnotation(method, annotationType)); + return mapping == null ? null : UriMapping.of(cleanup(mapping)); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.server.core.RawMappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method) + */ + @Override + public @Nullable String getMapping(@Nullable Class type, @Nullable Method method) { + + String[] mapping = method == null ? new String[0] : getMappingFrom(findMergedAnnotation(method, annotationType)); String typeMapping = getMapping(type); if (mapping.length == 0) { return typeMapping; } - return cleanup(typeMapping == null || "/".equals(typeMapping) ? mapping[0] : join(typeMapping, mapping[0])); + var result = typeMapping == null || "/".equals(typeMapping) + ? mapping[0] + : join(typeMapping, mapping[0]); + + return cleanup(result); } /** @@ -179,6 +188,17 @@ public class AnnotationMappingDiscoverer implements MappingDiscoverer { return params == null ? new String[0] : params; } + private @Nullable String getMapping(@Nullable Class type) { + + if (type == null) { + return null; + } + + String[] mapping = getMappingFrom(findMergedAnnotation(type, annotationType)); + + return mapping.length == 0 ? null : mapping[0]; + } + private String[] getMappingFrom(@Nullable Annotation annotation) { if (annotation == null) { diff --git a/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java b/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java index 34e4b38b..5b7b9444 100644 --- a/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/server/core/CachingMappingDiscoverer.java @@ -35,7 +35,7 @@ import org.springframework.util.StringUtils; */ public class CachingMappingDiscoverer implements MappingDiscoverer { - private static final Map MAPPINGS = new ConcurrentReferenceHashMap<>(); + private static final Map MAPPINGS = new ConcurrentReferenceHashMap<>(); private static final Map> METHODS = new ConcurrentReferenceHashMap<>(); private static final Map PARAMS = new ConcurrentReferenceHashMap<>(); private static final Map> CONSUMES = new ConcurrentReferenceHashMap<>(); @@ -52,38 +52,38 @@ public class CachingMappingDiscoverer implements MappingDiscoverer { /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.Class) */ @Nullable @Override - public String getMapping(Class type) { + public UriMapping getUriMapping(Class type) { String key = key(type, null); - return MAPPINGS.computeIfAbsent(key, __ -> delegate.getMapping(type)); + return MAPPINGS.computeIfAbsent(key, __ -> delegate.getUriMapping(type)); } /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.reflect.Method) */ @Nullable @Override - public String getMapping(Method method) { - return MAPPINGS.computeIfAbsent(key(method), __ -> delegate.getMapping(method)); + public UriMapping getUriMapping(Method method) { + return MAPPINGS.computeIfAbsent(key(method), __ -> delegate.getUriMapping(method)); } /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.Class, java.lang.reflect.Method) */ @Nullable @Override - public String getMapping(Class type, Method method) { + public UriMapping getUriMapping(Class type, Method method) { String key = key(type, method); - return MAPPINGS.computeIfAbsent(key, __ -> delegate.getMapping(type, method)); + return MAPPINGS.computeIfAbsent(key, __ -> delegate.getUriMapping(type, method)); } /* diff --git a/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java b/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java index 935cd0e9..71b7a60b 100644 --- a/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/server/core/MappingDiscoverer.java @@ -33,17 +33,6 @@ import org.springframework.http.MediaType; */ public interface MappingDiscoverer { - /** - * Returns the mapping associated with the given type. - * - * @param type must not be {@literal null}. - * @return the type-level mapping or {@literal null} in case none is present. - * @deprecated since 2.0, prefer {@link #getUriMapping(Class)} - */ - @Nullable - @Deprecated - String getMapping(Class type); - /** * Returns the mapping associated with the given type. * @@ -52,20 +41,7 @@ public interface MappingDiscoverer { * @since 2.0 */ @Nullable - default UriMapping getUriMapping(Class type) { - return UriMapping.of(getMapping(type)); - } - - /** - * Returns the mapping associated with the given {@link Method}. This will include the type-level mapping. - * - * @param method must not be {@literal null}. - * @return the method mapping including the type-level one or {@literal null} if neither of them present. - * @deprecated since 2.0, use {@link #getUriMapping(Method)} instead - */ - @Nullable - @Deprecated - String getMapping(Method method); + UriMapping getUriMapping(Class type); /** * Returns the mapping associated with the given {@link Method}. This will include the type-level mapping. @@ -75,22 +51,7 @@ public interface MappingDiscoverer { * @since 2.0 */ @Nullable - default UriMapping getUriMapping(Method method) { - return UriMapping.of(getMapping(method)); - } - - /** - * Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the - * mapping for a super type method being invoked on a sub-type with a type mapping. - * - * @param type must not be {@literal null}. - * @param method must not be {@literal null}. - * @return the method mapping including the type-level one or {@literal null} if neither of them present. - * @deprecated since 2.0, use {@link #getUriMapping(Class, Method)} instead - */ - @Nullable - @Deprecated - String getMapping(Class type, Method method); + UriMapping getUriMapping(Method method); /** * Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the @@ -102,9 +63,7 @@ public interface MappingDiscoverer { * @since 2.0 */ @Nullable - default UriMapping getUriMapping(Class type, Method method) { - return UriMapping.of(getMapping(type, method)); - } + UriMapping getUriMapping(Class type, Method method); /** * Returns the HTTP verbs for the given {@link Method} invoked on the given type. This can be used to build hypermedia diff --git a/src/main/java/org/springframework/hateoas/server/core/PropertyResolvingMappingDiscoverer.java b/src/main/java/org/springframework/hateoas/server/core/PropertyResolvingMappingDiscoverer.java index 20ae87ba..f5b79ead 100644 --- a/src/main/java/org/springframework/hateoas/server/core/PropertyResolvingMappingDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/server/core/PropertyResolvingMappingDiscoverer.java @@ -35,9 +35,9 @@ import org.springframework.web.context.WebApplicationContext; */ class PropertyResolvingMappingDiscoverer implements MappingDiscoverer { - private final MappingDiscoverer delegate; + private final RawMappingDiscoverer delegate; - PropertyResolvingMappingDiscoverer(MappingDiscoverer delegate) { + PropertyResolvingMappingDiscoverer(RawMappingDiscoverer delegate) { Assert.notNull(delegate, "Delegate MappingDiscoverer must not be null!"); @@ -46,32 +46,41 @@ class PropertyResolvingMappingDiscoverer implements MappingDiscoverer { /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.Class) */ @Nullable @Override - public String getMapping(Class type) { - return resolveProperties(delegate.getMapping(type)); + public UriMapping getUriMapping(Class type) { + + var mapping = delegate.getMapping(type, null); + + return mapping == null ? null : UriMapping.of(resolveProperties(mapping)); } /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.reflect.Method) */ @Nullable @Override - public String getMapping(Method method) { - return resolveProperties(delegate.getMapping(method)); + public UriMapping getUriMapping(Method method) { + + var mapping = delegate.getMapping(method.getDeclaringClass(), method); + + return mapping == null ? null : UriMapping.of(resolveProperties(mapping)); } /* * (non-Javadoc) - * @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method) + * @see org.springframework.hateoas.server.core.MappingDiscoverer#getUriMapping(java.lang.Class, java.lang.reflect.Method) */ @Nullable @Override - public String getMapping(Class type, Method method) { - return resolveProperties(delegate.getMapping(type, method)); + public UriMapping getUriMapping(Class type, Method method) { + + var mapping = delegate.getMapping(type, method); + + return mapping == null ? null : UriMapping.of(resolveProperties(mapping)); } /* diff --git a/src/main/java/org/springframework/hateoas/server/core/RawMappingDiscoverer.java b/src/main/java/org/springframework/hateoas/server/core/RawMappingDiscoverer.java new file mode 100644 index 00000000..6e10413c --- /dev/null +++ b/src/main/java/org/springframework/hateoas/server/core/RawMappingDiscoverer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 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 + * + * https://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.server.core; + +import java.lang.reflect.Method; + +import org.jspecify.annotations.Nullable; + +/** + * An extension of {@link MappingDiscoverer} that allows access to the raw {@link String}-based mapping before turning + * it into a {@link UriMapping}. + * + * @author Oliver Drotbohm + * @since 3.0 + */ +interface RawMappingDiscoverer extends MappingDiscoverer { + + /** + * Returns the raw URI mapping associated with the given method of the given class. + * + * @param type can be {@literal null}. + * @param method can be {@literal null}. + * @return can be {@literal null}. + */ + @Nullable + String getMapping(@Nullable Class type, @Nullable Method method); +} diff --git a/src/main/java/org/springframework/hateoas/server/core/SpringAffordanceBuilder.java b/src/main/java/org/springframework/hateoas/server/core/SpringAffordanceBuilder.java index 28aac5c6..2e37d472 100644 --- a/src/main/java/org/springframework/hateoas/server/core/SpringAffordanceBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/core/SpringAffordanceBuilder.java @@ -70,20 +70,6 @@ public class SpringAffordanceBuilder { .apply(Affordances.of(affordanceLink)); } - /** - * Returns the mapping for the given type's method. - * - * @param type must not be {@literal null}. - * @param method must not be {@literal null}. - * @return - * @deprecated since 2.0, use {@link #getUriMapping(Class, Method)} instead. - */ - @Nullable - @Deprecated - public static String getMapping(Class type, Method method) { - return DISCOVERER.getMapping(type, method); - } - /** * Returns the mapping for the given type's method. * diff --git a/src/main/java/org/springframework/hateoas/server/core/UriMapping.java b/src/main/java/org/springframework/hateoas/server/core/UriMapping.java index f1dc6009..798b383f 100644 --- a/src/main/java/org/springframework/hateoas/server/core/UriMapping.java +++ b/src/main/java/org/springframework/hateoas/server/core/UriMapping.java @@ -17,8 +17,10 @@ package org.springframework.hateoas.server.core; import static org.springframework.web.util.UriComponents.UriTemplateVariables.*; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -95,6 +97,43 @@ public class UriMapping { return this.variables; } + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return mapping + " " + variables; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(@Nullable Object obj) { + + if (obj == this) { + return true; + } + + if (!(obj instanceof UriMapping that)) { + return false; + } + + return Objects.equals(this.mapping, that.mapping) + && Objects.equals(this.variables, that.variables); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return Objects.hash(this.mapping, this.variables); + } + /** * All {@link MappingVariable}s contained in a {@link UriTemplate}. * @@ -105,7 +144,14 @@ public class UriMapping { private final List variables; public MappingVariables(UriTemplate template) { - this.variables = template.getVariableNames().stream().map(MappingVariable::of).collect(Collectors.toList()); + + this.variables = template.getVariableNames().stream() + .map(MappingVariable::of) + .collect(Collectors.toList()); + } + + private MappingVariables(List variables) { + this.variables = variables; } public boolean hasCapturingVariable() { @@ -129,6 +175,19 @@ public class UriMapping { .orElseThrow(() -> new IllegalArgumentException("No variable named " + name + " found!")); } + public MappingVariables and(MappingVariables other) { + + List result = new ArrayList<>(variables); + + for (MappingVariable variable : other) { + if (!result.contains(variable)) { + result.add(variable); + } + } + + return new MappingVariables(result); + } + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() @@ -137,6 +196,42 @@ public class UriMapping { public Iterator iterator() { return variables.iterator(); } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return variables.toString(); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(@Nullable Object obj) { + + if (obj == this) { + return true; + } + + if (!(obj instanceof MappingVariables that)) { + return false; + } + + return Objects.equals(this.variables, that.variables); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return Objects.hash(variables); + } } /** @@ -226,6 +321,43 @@ public class UriMapping { public Object getAbsentValue() { return composite ? TemplateVariable.segment(name).composite().toString() : SKIP_VALUE; } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return name + (composite ? "*" : ""); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(@Nullable Object obj) { + + if (obj == this) { + return true; + } + + if (!(obj instanceof MappingVariable that)) { + return false; + } + + return Objects.equals(this.name, that.name) + && this.composite == that.composite; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return Objects.hash(name, composite); + } } /** @@ -258,7 +390,6 @@ public class UriMapping { * @see java.util.function.Function#apply(java.lang.Object) */ @Override - @SuppressWarnings("null") public String apply(String source) { Matcher matcher = PATH_CAPTURE.matcher(source); diff --git a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java index 43cfbdde..4a6fbb51 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java @@ -84,8 +84,8 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport