diff --git a/src/main/java/org/springframework/hateoas/Links.java b/src/main/java/org/springframework/hateoas/Links.java index 8930d617..63caeaec 100644 --- a/src/main/java/org/springframework/hateoas/Links.java +++ b/src/main/java/org/springframework/hateoas/Links.java @@ -136,7 +136,7 @@ public class Links implements Iterable { /** * Adds the given links if the given condition is {@literal true}. The given {@link Supplier}s will only be resolved - * if the given condition is true. Essentially syntactic sugar to write:
+ * if the given condition is {@literal true}. Essentially syntactic sugar to write:
* * if (a > 3) { * links = links.and(…); @@ -154,9 +154,29 @@ public class Links implements Iterable { Assert.notNull(links, "Links must not be null!"); - return condition // - ? and(Arrays.stream(links).map(Supplier::get).collect(Collectors.toList())) // - : this; + return andIf(condition, Stream.of(links).map(Supplier::get)); + } + + /** + * Adds the given links if the given condition is {@literal true}. The given {@link Stream} will only be resolved if + * the given condition is {@literal true}. Essentially syntactic sugar to write:
+ * + * if (a > 3) { + * links = links.and(…); + * } + * as + * links = link.andIf(a > 3, …); + * + * + * @param condition + * @param links must not be {@literal null}. + * @return + */ + public final Links andIf(boolean condition, Stream links) { + + Assert.notNull(links, "Links must not be null!"); + + return condition ? and(links.collect(Collectors.toList())) : this; } /** @@ -176,6 +196,19 @@ public class Links implements Iterable { return Links.of(newLinks); } + /** + * Creates a new {@link Links} instance with all given {@link Link}s added. For conditional adding see + * {@link #merge(Iterable)}. + * + * @param links must not be {@literal null}. + * @return + * @see #merge(Iterable) + * @see #merge(MergeMode, Iterable) + */ + public Links and(Stream links) { + return and(links.collect(Collectors.toList())); + } + /** * Merges the current {@link Links} with the given ones, skipping {@link Link}s already contained in the current * instance. For unconditional combination see {@link #and(Link...)}. @@ -191,12 +224,25 @@ public class Links implements Iterable { /** * Merges the current {@link Links} with the given ones, skipping {@link Link}s already contained in the current - * instance. For unconditional combination see {@link #and(Link...)}. + * instance. For unconditional combination see {@link #and(Stream)}. * * @param links the {@link Link}s to be merged, must not be {@literal null}. * @return * @see MergeMode#SKIP_BY_EQUALITY - * @see #and(Link...) + * @see #and(Stream) + */ + public Links merge(Stream links) { + return merge(links.collect(Collectors.toList())); + } + + /** + * Merges the current {@link Links} with the given ones, skipping {@link Link}s already contained in the current + * instance. For unconditional combination see {@link #and(Iterable)}. + * + * @param links the {@link Link}s to be merged, must not be {@literal null}. + * @return + * @see MergeMode#SKIP_BY_EQUALITY + * @see #and(Iterable) */ public Links merge(Iterable links) { return merge(MergeMode.SKIP_BY_EQUALITY, links); @@ -213,6 +259,17 @@ public class Links implements Iterable { return merge(mode, Arrays.asList(links)); } + /** + * Merges the current {@link Links} with the given ones applying the given {@link MergeMode}. + * + * @param mode must not be {@literal null}. + * @param links must not be {@literal null}. + * @return + */ + public Links merge(MergeMode mode, Stream links) { + return merge(mode, links.collect(Collectors.toList())); + } + /** * Merges the current {@link Links} with the given ones applying the given {@link MergeMode}. * diff --git a/src/test/java/org/springframework/hateoas/LinksUnitTest.java b/src/test/java/org/springframework/hateoas/LinksUnitTest.java index 313965e9..43f4f7ec 100755 --- a/src/test/java/org/springframework/hateoas/LinksUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinksUnitTest.java @@ -17,10 +17,17 @@ package org.springframework.hateoas; import static org.assertj.core.api.Assertions.*; -import java.util.Arrays; -import java.util.Optional; +import lombok.Value; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.springframework.hateoas.Links.MergeMode; import org.springframework.util.StringUtils; /** @@ -151,4 +158,43 @@ class LinksUnitTest { throw new IllegalStateException(); })).doesNotThrowAnyException(); } + + @TestFactory // #1340 + Stream addsStreamOfLinks() { + + Links links = Links.NONE; + Link link = Link.of("/foo"); + + List sources = Arrays.asList(// + NamedLinks.of("via varargs", links.and(link)), // + NamedLinks.of("via Iterable", links.and(Arrays.asList(link))), // + NamedLinks.of("via Stream", links.and(Stream.of(link)))); + + return DynamicTest.stream(sources.iterator(), NamedLinks::getName, + it -> assertThat(it.links.getRequiredLink(IanaLinkRelations.SELF)).isNotNull()); + } + + @TestFactory // #1340 + Stream mergesStreamOfLinks() { + + Links links = Links.NONE.and(Link.of("/foo")); + Link same = Link.of("/foo"); + Link sameRel = Link.of("/bar"); + + List sources = Arrays.asList(// + NamedLinks.of("merge same via varargs", links.merge(same)), + NamedLinks.of("merge same rel via varargs", links.merge(MergeMode.SKIP_BY_REL, sameRel)), + NamedLinks.of("merge same via Stream", links.merge(Stream.of(same))), + NamedLinks.of("merge same rel via Stream", links.merge(MergeMode.SKIP_BY_REL, Stream.of(sameRel)))); + + return DynamicTest.stream(sources.iterator(), NamedLinks::getName, + it -> assertThat(it.links).hasSize(1) // + .element(0).extracting(Link::getHref).isEqualTo("/foo")); + } + + @Value(staticConstructor = "of") + static class NamedLinks { + String name; + Links links; + } }