From 1493b4d7a7c0fb32706e4f42c01a9bd6f8c7d221 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Sat, 10 Jan 2026 22:57:52 +0000 Subject: [PATCH 1/3] Add copy constructor to BiFunctionClassMap for deep cloning --- .../util/classmap/BiFunctionClassMap.java | 9 ++ .../util/classmap/BiFunctionClassMapTest.java | 102 ++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java b/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java index 76d0558e..a1395f11 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; +import java.util.function.Function; import pt.up.fe.specs.util.exceptions.NotImplementedException; import pt.up.fe.specs.util.utilities.ClassMapper; @@ -41,6 +42,14 @@ public BiFunctionClassMap() { this.classMapper = new ClassMapper(); } + public BiFunctionClassMap(BiFunctionClassMap other) { + this.map = new HashMap<>(); + for (var keyPair : other.map.entrySet()) { + this.map.put(keyPair.getKey(), (BiFunction) keyPair.getValue()); + } + this.classMapper = new ClassMapper(other.classMapper); + } + /** * Associates the specified value with the specified key. * diff --git a/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java b/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java index 46d4bb11..d6d62d0f 100644 --- a/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java +++ b/SpecsUtils/test/pt/up/fe/specs/util/classmap/BiFunctionClassMapTest.java @@ -42,6 +42,108 @@ void testDefaultConstructor() { assertThatThrownBy(() -> map.apply(new Object(), "test")) .hasMessageContaining("BiConsumer not defined for class"); } + + @Test + @DisplayName("Should create copy with all mappings from original") + void testCopyConstructor() { + numberMap.put(Integer.class, (i, s) -> "Int: " + i + "-" + s); + numberMap.put(Double.class, (d, s) -> "Double: " + d + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + assertThat(copy.apply(42, "test")).isEqualTo("Int: 42-test"); + assertThat(copy.apply(3.14, "test")).isEqualTo("Double: 3.14-test"); + } + + @Test + @DisplayName("Should create independent copy - changes to original don't affect copy") + void testCopyIndependenceOriginalToNew() { + numberMap.put(Integer.class, (i, s) -> "Original: " + i + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + // Modify original + numberMap.put(Integer.class, (i, s) -> "Modified: " + i + "-" + s); + numberMap.put(Double.class, (d, s) -> "New: " + d + "-" + s); + + // Copy should retain original behavior + assertThat(copy.apply(42, "test")).isEqualTo("Original: 42-test"); + + // Copy shouldn't have the new Double mapping + assertThatThrownBy(() -> copy.apply(3.14, "test")) + .hasMessageContaining("BiConsumer not defined for class"); + } + + @Test + @DisplayName("Should create independent copy - changes to copy don't affect original") + void testCopyIndependenceNewToOriginal() { + numberMap.put(Integer.class, (i, s) -> "Original: " + i + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + // Modify copy + copy.put(Integer.class, (i, s) -> "Modified: " + i + "-" + s); + copy.put(Double.class, (d, s) -> "New: " + d + "-" + s); + + // Original should retain original behavior + assertThat(numberMap.apply(42, "test")).isEqualTo("Original: 42-test"); + + // Original shouldn't have the new Double mapping + assertThatThrownBy(() -> numberMap.apply(3.14, "test")) + .hasMessageContaining("BiConsumer not defined for class"); + } + + @Test + @DisplayName("Should copy empty map successfully") + void testCopyEmptyMap() { + BiFunctionClassMap emptyMap = new BiFunctionClassMap<>(); + BiFunctionClassMap copy = new BiFunctionClassMap<>(emptyMap); + + assertThatThrownBy(() -> copy.apply(42, "test")) + .hasMessageContaining("BiConsumer not defined for class"); + } + + @Test + @DisplayName("Should copy classMapper for hierarchy resolution") + void testCopyClassMapper() { + numberMap.put(Number.class, (n, s) -> "Number: " + n + "-" + s); + numberMap.put(Integer.class, (i, s) -> "Integer: " + i + "-" + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(numberMap); + + // Hierarchy resolution should work in copy + assertThat(copy.apply(42, "test")).isEqualTo("Integer: 42-test"); + assertThat(copy.apply(3.14, "test")).isEqualTo("Number: 3.14-test"); + assertThat(copy.apply(42L, "test")).isEqualTo("Number: 42-test"); + } + + @Test + @DisplayName("Should handle covariant return type in copy constructor") + void testCovariantCopy() { + BiFunctionClassMap intResultMap = new BiFunctionClassMap<>(); + intResultMap.put(Integer.class, (i, s) -> s.length() + i.intValue()); + + // Copy with covariant return type (Integer extends Number) + BiFunctionClassMap copy = new BiFunctionClassMap<>(intResultMap); + + Number result = copy.apply(10, "test"); + assertThat(result).isEqualTo(14); + } + + @Test + @DisplayName("Should copy map with multiple hierarchy levels") + void testCopyComplexHierarchy() { + BiFunctionClassMap exceptionMap = new BiFunctionClassMap<>(); + exceptionMap.put(Exception.class, (e, s) -> "Exception: " + s); + exceptionMap.put(RuntimeException.class, (e, s) -> "Runtime: " + s); + exceptionMap.put(IllegalArgumentException.class, (e, s) -> "IllegalArg: " + s); + + BiFunctionClassMap copy = new BiFunctionClassMap<>(exceptionMap); + + assertThat(copy.apply(new Exception(), "test")).isEqualTo("Exception: test"); + assertThat(copy.apply(new RuntimeException(), "test")).isEqualTo("Runtime: test"); + assertThat(copy.apply(new IllegalArgumentException(), "test")).isEqualTo("IllegalArg: test"); + } } @Nested From 543a0e1a998cecca33dbeeac96954274b993a0c2 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Sun, 11 Jan 2026 21:57:57 +0000 Subject: [PATCH 2/3] Update checkout action to v6 in nightly workflow --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f6064405..5023bbb4 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout specs-java-libs - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Java uses: actions/setup-java@v4 From ff8ea01dd73e8c9613a78897ecf09b649be94f13 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Wed, 21 Jan 2026 23:17:31 +0000 Subject: [PATCH 3/3] Remove unused import for Function in BiFunctionClassMap --- .../src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java | 1 - 1 file changed, 1 deletion(-) diff --git a/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java b/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java index a1395f11..2599f147 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java @@ -17,7 +17,6 @@ import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; -import java.util.function.Function; import pt.up.fe.specs.util.exceptions.NotImplementedException; import pt.up.fe.specs.util.utilities.ClassMapper;