From 5f211c197f592d819c53b1b95a21c8ddd8a498a4 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Mon, 12 Jan 2026 10:12:46 +0100 Subject: [PATCH 1/2] add identity map --- .../toolbox/util/MapByIdentity.java | 51 ++++++ .../toolbox/util/MapByIdentityTest.java | 154 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/util/MapByIdentity.java create mode 100644 src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java diff --git a/src/main/java/it/aboutbits/springboot/toolbox/util/MapByIdentity.java b/src/main/java/it/aboutbits/springboot/toolbox/util/MapByIdentity.java new file mode 100644 index 0000000..cfff13a --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/util/MapByIdentity.java @@ -0,0 +1,51 @@ +package it.aboutbits.springboot.toolbox.util; + +import it.aboutbits.springboot.toolbox.type.identity.EntityId; +import it.aboutbits.springboot.toolbox.type.identity.Identified; +import org.jspecify.annotations.NullMarked; +import org.springframework.data.util.Streamable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@NullMarked +public class MapByIdentity, E extends Identified> extends HashMap { + public MapByIdentity(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + public MapByIdentity(int initialCapacity) { + super(initialCapacity); + } + + public MapByIdentity() { + super(); + } + + public MapByIdentity(Map m) { + super(m); + } + + public static , E extends Identified> MapByIdentity of(Collection items) { + var map = new MapByIdentity(items.size()); + items.forEach(item -> map.put(item.getId(), item)); + return map; + } + + public static , E extends Identified> MapByIdentity of(Stream items) { + var collected = items.collect(Collectors.toMap( + Identified::getId, + Function.identity() + )); + + return new MapByIdentity<>(collected); + } + + public static , E extends Identified> MapByIdentity of(Streamable items) { + return of(items.toList()); + } +} diff --git a/src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java b/src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java new file mode 100644 index 0000000..09a6336 --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java @@ -0,0 +1,154 @@ +package it.aboutbits.springboot.toolbox.util; + +import it.aboutbits.springboot.toolbox.type.identity.EntityId; +import it.aboutbits.springboot.toolbox.type.identity.Identified; +import org.jspecify.annotations.NullMarked; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.data.util.Streamable; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +@NullMarked +class MapByIdentityTest { + @Nested + class Of { + @Test + void collection() { + // given + var id1 = new TestEntityId(1); + var id2 = new TestEntityId(2); + + var entity1 = new TestEntity(id1); + var entity2 = new TestEntity(id2); + + var entities = List.of(entity1, entity2); + + // when + var result = MapByIdentity.of(entities); + + // then + assertThat(result).hasSize(2) + .containsEntry(id1, entity1) + .containsEntry(id2, entity2); + } + + @Test + void stream() { + // given + var id1 = new TestEntityId(1); + var id2 = new TestEntityId(2); + + var entity1 = new TestEntity(id1); + var entity2 = new TestEntity(id2); + + var entityStream = Stream.of(entity1, entity2); + + // when + var result = MapByIdentity.of(entityStream); + + // then + assertThat(result).hasSize(2) + .containsEntry(id1, entity1) + .containsEntry(id2, entity2); + } + + @Test + void streamable() { + // given + var id1 = new TestEntityId(1); + var id2 = new TestEntityId(2); + + var entity1 = new TestEntity(id1); + var entity2 = new TestEntity(id2); + + var streamable = Streamable.of(entity1, entity2); + + // when + var result = MapByIdentity.of(streamable); + + // then + assertThat(result).hasSize(2) + .containsEntry(id1, entity1) + .containsEntry(id2, entity2); + } + + @Test + void emptyCollection() { + // given + var entities = List.of(); + + // when + var result = MapByIdentity.of(entities); + + // then + assertThat(result).isEmpty(); + } + + @Test + void emptyStream() { + // given + var emptyStream = Stream.empty(); + + // when + var result = MapByIdentity.of(emptyStream); + + // then + assertThat(result).isEmpty(); + } + + @Test + void emptyStreamable() { + // given + var emptyStreamable = Streamable.empty(); + + // when + var result = MapByIdentity.of(emptyStreamable); + + // then + assertThat(result).isEmpty(); + } + } + + private static class TestEntityId implements EntityId { + private final Integer value; + + public TestEntityId(Integer value) { + this.value = value; + } + + @Override + public Integer value() { + return value; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof TestEntityId)) return false; + var other = (TestEntityId) obj; + return value.equals(other.value); + } + } + + private static class TestEntity implements Identified { + private final TestEntityId id; + + public TestEntity(TestEntityId id) { + this.id = id; + } + + @Override + public TestEntityId getId() { + return id; + } + } +} From 1e3b0f1a680b8b07dcbdf9dae328589bc1b5d5ff Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Mon, 12 Jan 2026 10:18:47 +0100 Subject: [PATCH 2/2] fix tests --- .../toolbox/util/MapByIdentityTest.java | 61 +++++++------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java b/src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java index 09a6336..b13a52b 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/util/MapByIdentityTest.java @@ -2,6 +2,7 @@ import it.aboutbits.springboot.toolbox.type.identity.EntityId; import it.aboutbits.springboot.toolbox.type.identity.Identified; +import lombok.Getter; import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -19,8 +20,8 @@ class Of { @Test void collection() { // given - var id1 = new TestEntityId(1); - var id2 = new TestEntityId(2); + var id1 = new TestEntity.ID(1L); + var id2 = new TestEntity.ID(2L); var entity1 = new TestEntity(id1); var entity2 = new TestEntity(id2); @@ -39,8 +40,8 @@ void collection() { @Test void stream() { // given - var id1 = new TestEntityId(1); - var id2 = new TestEntityId(2); + var id1 = new TestEntity.ID(1L); + var id2 = new TestEntity.ID(2L); var entity1 = new TestEntity(id1); var entity2 = new TestEntity(id2); @@ -59,8 +60,8 @@ void stream() { @Test void streamable() { // given - var id1 = new TestEntityId(1); - var id2 = new TestEntityId(2); + var id1 = new TestEntity.ID(1L); + var id2 = new TestEntity.ID(2L); var entity1 = new TestEntity(id1); var entity2 = new TestEntity(id2); @@ -113,42 +114,26 @@ void emptyStreamable() { } } - private static class TestEntityId implements EntityId { - private final Integer value; + @Getter + private static class TestEntity implements Identified { + private final ID id; - public TestEntityId(Integer value) { - this.value = value; - } - - @Override - public Integer value() { - return value; - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof TestEntityId)) return false; - var other = (TestEntityId) obj; - return value.equals(other.value); - } - } - - private static class TestEntity implements Identified { - private final TestEntityId id; - - public TestEntity(TestEntityId id) { + TestEntity(ID id) { this.id = id; } - @Override - public TestEntityId getId() { - return id; + public record ID( + Long value + ) implements EntityId, Comparable { + @Override + public String toString() { + return String.valueOf(value()); + } + + @Override + public int compareTo(ID other) { + return Long.compare(this.value, other.value); + } } } }