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 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..2599f147 100644 --- a/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java +++ b/SpecsUtils/src/pt/up/fe/specs/util/classmap/BiFunctionClassMap.java @@ -41,6 +41,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