From f85892789dfc8d5eb42157400cf930c3c30fb5aa Mon Sep 17 00:00:00 2001 From: trydofor Date: Wed, 22 Jan 2025 10:22:56 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20TypedRef=20to=20ref=20k-v=20type=20?= =?UTF-8?q?#50?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 3 - .../pro/fessional/mirana/best/TypedKey.java | 16 ++- .../pro/fessional/mirana/best/TypedRef.java | 72 +++++++++++ .../pro/fessional/mirana/best/TypedReg.java | 22 ++-- .../fessional/mirana/best/DummyBlockTest.java | 64 ++++++++++ .../fessional/mirana/best/TypedKeyTest.java | 113 ++++++++++++++++++ .../fessional/mirana/best/TypedRefTest.java | 96 +++++++++++++++ .../fessional/mirana/best/TypedRegTest.java | 89 +++++++++++++- 8 files changed, 451 insertions(+), 24 deletions(-) create mode 100644 src/main/java/pro/fessional/mirana/best/TypedRef.java create mode 100644 src/test/java/pro/fessional/mirana/best/DummyBlockTest.java create mode 100644 src/test/java/pro/fessional/mirana/best/TypedKeyTest.java create mode 100644 src/test/java/pro/fessional/mirana/best/TypedRefTest.java diff --git a/pom.xml b/pom.xml index 7d031f9..eff31ca 100644 --- a/pom.xml +++ b/pom.xml @@ -281,11 +281,8 @@ - **/best/Dummy* **/best/Param* - **/best/Typed* - **/data/DataResult.* **/data/Null* **/data/Q.* **/data/Q$* diff --git a/src/main/java/pro/fessional/mirana/best/TypedKey.java b/src/main/java/pro/fessional/mirana/best/TypedKey.java index bca8df9..1274ee3 100644 --- a/src/main/java/pro/fessional/mirana/best/TypedKey.java +++ b/src/main/java/pro/fessional/mirana/best/TypedKey.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Function; /** *
@@ -44,15 +45,18 @@ protected TypedKey() {
     }
 
     @SuppressWarnings("unchecked")
-    @Nullable
-    public V get(@NotNull Map map) throws ClassCastException {
-        final Object o = map.get(this);
-        return (V) o;
+    @Contract("_,true->!null")
+    public V get(@NotNull Function,?> map, boolean nonnull) throws ClassCastException {
+        Object obj = map.apply(this);
+        if (obj == null && nonnull) {
+            throw new ClassCastException("null cast to nonnull");
+        }
+        return (V) obj;
     }
 
     @Contract("_,!null ->!null")
-    public V getOr(@NotNull Map map, V elze) throws ClassCastException {
-        final V obj = get(map);
+    public V getOr(@NotNull Function,?> map, V elze) throws ClassCastException {
+        final V obj = get(map, false);
         return obj != null ? obj : elze;
     }
 
diff --git a/src/main/java/pro/fessional/mirana/best/TypedRef.java b/src/main/java/pro/fessional/mirana/best/TypedRef.java
new file mode 100644
index 0000000..63abdf0
--- /dev/null
+++ b/src/main/java/pro/fessional/mirana/best/TypedRef.java
@@ -0,0 +1,72 @@
+package pro.fessional.mirana.best;
+
+
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * 
+ * Usage: mark value and its ref type.
+ * {@code
+ * Map map = new HashMap<>();
+ * map.put("key", 42);
+ * TypedRef ref = new TypedRef<>("key");
+ * Integer result = ref.get(map);
+ * }
+ * 
+ * + * @author trydofor + * @since 2025-01-21 + */ +public class TypedRef { + @NotNull + public final V value; + + public TypedRef(@NotNull V value) { + this.value = value; + } + + @SuppressWarnings("unchecked") + @Contract("_,true->!null") + public R get(@NotNull Function map, boolean nonnull) throws ClassCastException { + Object obj = map.apply(value); + if (obj == null && nonnull) { + throw new ClassCastException("null cast to nonnull"); + } + return (R) obj; + } + + @Contract("_,!null ->!null") + public R getOr(@NotNull Function map, R elze) throws ClassCastException { + final R obj = get(map, false); + return obj != null ? obj : elze; + } + + @SuppressWarnings("unchecked") + @Contract("_,!null ->!null") + public R tryOr(@Nullable Object obj, R elze) throws ClassCastException { + return obj != null ? (R) obj : elze; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TypedRef typedRef = (TypedRef) o; + return Objects.equals(value, typedRef.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + @Override + public String toString() { + return value.toString(); + } +} diff --git a/src/main/java/pro/fessional/mirana/best/TypedReg.java b/src/main/java/pro/fessional/mirana/best/TypedReg.java index 6b911fb..7a4693c 100644 --- a/src/main/java/pro/fessional/mirana/best/TypedReg.java +++ b/src/main/java/pro/fessional/mirana/best/TypedReg.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Function; /** *
@@ -47,6 +48,10 @@ protected TypedReg() {
         INSTANCE.put(clz.getName(), this);
     }
 
+    public Key key(K key) {
+        return new Key<>(this, key);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -100,10 +105,6 @@ public static  TypedReg deserialize(@NotNull String clz, boolean non
         }
     }
 
-    public static  Key key(@NotNull TypedReg reg, K key) {
-        return new Key<>(reg, key);
-    }
-
     public static class Key {
         @NotNull
         public final TypedReg reg;
@@ -116,14 +117,17 @@ public Key(@NotNull TypedReg reg, K key) {
 
         @SuppressWarnings("unchecked")
         @Nullable
-        public V get(@NotNull Map map) throws ClassCastException {
-            final Object o = map.get(this);
-            return (V) o;
+        public V get(@NotNull Function, ?> map, boolean nonnull) throws ClassCastException {
+            Object obj = map.apply(this);
+            if (obj == null && nonnull) {
+                throw new ClassCastException("null cast to nonnull");
+            }
+            return (V) obj;
         }
 
         @Contract("_,!null ->!null")
-        public V getOr(@NotNull Map map, V elze) throws ClassCastException {
-            final V obj = get(map);
+        public V getOr(@NotNull Function, ?> map, V elze) throws ClassCastException {
+            final V obj = get(map, false);
             return obj != null ? obj : elze;
         }
 
diff --git a/src/test/java/pro/fessional/mirana/best/DummyBlockTest.java b/src/test/java/pro/fessional/mirana/best/DummyBlockTest.java
new file mode 100644
index 0000000..9f6bce3
--- /dev/null
+++ b/src/test/java/pro/fessional/mirana/best/DummyBlockTest.java
@@ -0,0 +1,64 @@
+package pro.fessional.mirana.best;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DummyBlockTest {
+
+    @Test
+    void testIgnore() {
+        AtomicReference captured = new AtomicReference<>();
+        DummyBlock.TweakIgnore.tweakGlobal(captured::set);
+
+        Throwable testException = new RuntimeException("Test exception");
+        DummyBlock.ignore(testException);
+
+        assertEquals(testException, captured.get());
+
+        DummyBlock.TweakIgnore.resetGlobal();
+    }
+
+    @Test
+    void testIgnoreWithoutHandler() {
+        // Test ignore with no handler set
+        Throwable testException = new RuntimeException("Test exception");
+        assertDoesNotThrow(() -> DummyBlock.ignore(testException));
+    }
+
+    @Test
+    void testEmpty() {
+        assertDoesNotThrow(DummyBlock::empty);
+    }
+
+    @Test
+    void testNever() {
+        IllegalStateException exception = assertThrows(IllegalStateException.class, DummyBlock::never);
+        assertEquals("should NOT invoke NEVER", exception.getMessage());
+
+        exception = assertThrows(IllegalStateException.class, () -> DummyBlock.never("Custom message"));
+        assertEquals("should NOT invoke NEVER:Custom message", exception.getMessage());
+    }
+
+    @Test
+    void testTodo() {
+        IllegalStateException exception = assertThrows(IllegalStateException.class, DummyBlock::todo);
+        assertEquals("should NOT invoke TODO", exception.getMessage());
+
+        exception = assertThrows(IllegalStateException.class, () -> DummyBlock.todo("Custom message"));
+        assertEquals("should NOT invoke TODO:Custom message", exception.getMessage());
+    }
+
+    @Test
+    void testFixme() {
+        IllegalStateException exception = assertThrows(IllegalStateException.class, DummyBlock::fixme);
+        assertEquals("should NOT invoke FIXME", exception.getMessage());
+
+        exception = assertThrows(IllegalStateException.class, () -> DummyBlock.fixme("Custom message"));
+        assertEquals("should NOT invoke FIXME:Custom message", exception.getMessage());
+    }
+}
diff --git a/src/test/java/pro/fessional/mirana/best/TypedKeyTest.java b/src/test/java/pro/fessional/mirana/best/TypedKeyTest.java
new file mode 100644
index 0000000..268f5e2
--- /dev/null
+++ b/src/test/java/pro/fessional/mirana/best/TypedKeyTest.java
@@ -0,0 +1,113 @@
+package pro.fessional.mirana.best;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @author trydofor
+ * @since 2025-01-22
+ */
+class TypedKeyTest {
+    static class TestTypedKey extends TypedKey {}
+
+    @Test
+    void testConstructor() {
+        TypedKey key = new TestTypedKey();
+        assertNotNull(key.regType);
+        assertNotNull(key.valType);
+        assertEquals(String.class, key.valType);
+    }
+
+    @Test
+    void testGet() {
+        TypedKey key = new TestTypedKey();
+        Map, Object> map = new HashMap<>();
+        map.put(key, "value");
+
+        String value = key.get(map::get, true);
+        assertEquals("value", value);
+    }
+
+    @Test
+    void testGetOr() {
+        TypedKey key = new TestTypedKey();
+        Map, Object> map = new HashMap<>();
+
+        // Test with no value in the map
+        String defaultValue = "default";
+        String value = key.getOr(map::get, defaultValue);
+        assertEquals(defaultValue, value);
+
+        // Test with a value in the map
+        map.put(key, "value");
+        value = key.getOr(map::get, defaultValue);
+        assertEquals("value", value);
+    }
+
+    @Test
+    void testTryOr() {
+        TypedKey key = new TestTypedKey();
+
+        // Test with null object
+        String defaultValue = "default";
+        String value = key.tryOr(null, defaultValue);
+        assertEquals(defaultValue, value);
+
+        // Test with non-null object
+        value = key.tryOr("value", defaultValue);
+        assertEquals("value", value);
+    }
+
+    @Test
+    void testSerializeDeserialize() {
+        TypedKey key = new TestTypedKey();
+        String serialized = key.serialize();
+
+        TypedKey deserializedKey = TypedKey.deserialize(serialized);
+        assertNotNull(deserializedKey);
+        assertEquals(key.regType, deserializedKey.regType);
+    }
+
+    @Test
+    void testDeserializeNonNull() {
+        TypedKey key = new TestTypedKey();
+        String serialized = key.serialize();
+
+        TypedKey deserializedKey = TypedKey.deserialize(serialized, true);
+        assertNotNull(deserializedKey);
+        assertSame(key, deserializedKey);
+    }
+
+    @Test
+    void testDeserializeNullable() {
+        TypedKey deserializedKey = TypedKey.deserialize("nonexistent", false);
+        assertNull(deserializedKey);
+    }
+
+    @Test
+    void testEqualsAndHashCode() {
+        TypedKey key1 = new TestTypedKey();
+        TypedKey key2 = new TestTypedKey();
+
+        assertEquals(key1, key1); // Reflexive
+        assertNotEquals(key1, key2); // Different instances
+        assertEquals(key1.hashCode(), key1.hashCode());
+    }
+
+    @Test
+    void testToString() {
+        TypedKey key = new TestTypedKey();
+        String toString = key.toString();
+        assertTrue(toString.contains("regType"));
+        assertTrue(toString.contains("valType"));
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/pro/fessional/mirana/best/TypedRefTest.java b/src/test/java/pro/fessional/mirana/best/TypedRefTest.java
new file mode 100644
index 0000000..753afe8
--- /dev/null
+++ b/src/test/java/pro/fessional/mirana/best/TypedRefTest.java
@@ -0,0 +1,96 @@
+package pro.fessional.mirana.best;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * @author trydofor
+ * @since 2025-01-22
+ */
+class TypedRefTest {
+    @Test
+    void testConstructor() {
+        TypedRef ref = new TypedRef<>("key");
+        assertNotNull(ref.value);
+        assertEquals("key", ref.value);
+    }
+
+    @Test
+    void testGet() {
+        Map map = new HashMap<>();
+        map.put("key", 42);
+
+        TypedRef ref = new TypedRef<>("key");
+        Integer result = ref.get(map::get, true);
+        assertNotNull(result);
+        assertEquals(42, result);
+    }
+
+    @Test
+    void testGetWithMissingKey() {
+        Map map = new HashMap<>();
+        map.put("anotherKey", 42);
+
+        TypedRef ref = new TypedRef<>("key");
+        Integer result = ref.get(map::get, false);
+        assertNull(result);
+    }
+
+    @Test
+    void testGetOrWithExistingKey() {
+        Map map = new HashMap<>();
+        map.put("key", 42);
+
+        TypedRef ref = new TypedRef<>("key");
+        Integer result = ref.getOr(map::get, 100);
+        assertEquals(42, result);
+    }
+
+    @Test
+    void testGetOrWithMissingKey() {
+        Map map = new HashMap<>();
+
+        TypedRef ref = new TypedRef<>("key");
+        Integer result = ref.getOr(map::get, 100);
+        assertEquals(100, result);
+    }
+
+    @Test
+    void testTryOrWithNonNullObject() {
+        TypedRef ref = new TypedRef<>("key");
+        Integer result = ref.tryOr(42, 100);
+        assertEquals(42, result);
+    }
+
+    @Test
+    void testTryOrWithNullObject() {
+        TypedRef ref = new TypedRef<>("key");
+        Integer result = ref.tryOr(null, 100);
+        assertEquals(100, result);
+    }
+
+    @Test
+    void testEqualsAndHashCode() {
+        TypedRef ref1 = new TypedRef<>("key");
+        TypedRef ref2 = new TypedRef<>("key");
+        TypedRef ref3 = new TypedRef<>("anotherKey");
+
+        assertEquals(ref1, ref2);
+        assertNotEquals(ref1, ref3);
+        assertEquals(ref1.hashCode(), ref2.hashCode());
+        assertNotEquals(ref1.hashCode(), ref3.hashCode());
+    }
+
+    @Test
+    void testToString() {
+        TypedRef ref = new TypedRef<>("key");
+        assertEquals("key", ref.toString());
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/pro/fessional/mirana/best/TypedRegTest.java b/src/test/java/pro/fessional/mirana/best/TypedRegTest.java
index a582316..43063aa 100644
--- a/src/test/java/pro/fessional/mirana/best/TypedRegTest.java
+++ b/src/test/java/pro/fessional/mirana/best/TypedRegTest.java
@@ -1,8 +1,16 @@
 package pro.fessional.mirana.best;
 
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 /**
  * @author trydofor
  * @since 2022-10-30
@@ -14,16 +22,85 @@ class TypedRegTest {
 
     @Test
     void key() {
-        Assertions.assertEquals(Test1.getClass(), Test1.regType);
-        Assertions.assertEquals(Integer.class, Test1.keyType);
-        Assertions.assertEquals(String.class, Test1.valType);
+        assertEquals(Test1.getClass(), Test1.regType);
+        assertEquals(Integer.class, Test1.keyType);
+        assertEquals(String.class, Test1.valType);
 
         String s1 = Test1.serialize();
         TypedReg t1 = TypedReg.deserialize(s1);
-        Assertions.assertSame(Test1, t1);
+        assertSame(Test1, t1);
 
         String s2 = Test2.serialize();
         TypedReg t2 = TypedReg.deserialize(s2);
-        Assertions.assertSame(Test2, t2);
+        assertSame(Test2, t2);
+    }
+
+
+    // 测试构造函数
+    @Test
+    void testConstructor() {
+        TypedReg reg = new TypedReg() {};
+        assertNotNull(reg.regType);
+        assertNotNull(reg.keyType);
+        assertNotNull(reg.valType);
+        assertEquals("java.lang.Integer", reg.keyType.getTypeName());
+        assertEquals("java.lang.String", reg.valType.getTypeName());
+    }
+
+    // 测试 Key 类的行为
+    @Test
+    void testKeyBehavior() {
+        TypedReg reg = new TypedReg() {};
+        TypedReg.Key key = reg.key(1);
+
+        assertNotNull(key);
+        assertEquals(reg, key.reg);
+        assertEquals(1, key.key);
+
+        Map, String> map = new HashMap<>();
+        map.put(key, "value");
+
+        assertEquals("value", key.get(map::get, true));
+        map.clear();
+        assertEquals("default", key.getOr(map::get, "default"));
+        assertEquals("fallback", key.tryOr("fallback", "default"));
+    }
+
+    // 测试 Key 的 equals 和 hashCode
+    @Test
+    void testKeyEqualsAndHashCode() {
+        TypedReg reg = new TypedReg() {};
+        TypedReg.Key key1 = reg.key(1);
+        TypedReg.Key key2 = reg.key(1);
+
+        assertEquals(key1, key2);
+        assertEquals(key1.hashCode(), key2.hashCode());
+    }
+
+    // 测试 serialize 和 deserialize
+    @Test
+    void testSerializeAndDeserialize() {
+        TypedReg reg = new TypedReg() {};
+        String serialized = reg.serialize();
+
+        assertNotNull(serialized);
+
+        TypedReg deserialized = TypedReg.deserialize(serialized);
+        assertEquals(reg, deserialized);
+    }
+
+    // 测试 deserialize 的异常情况
+    @Test
+    void testDeserializeException() {
+        assertThrows(ClassCastException.class, () -> TypedReg.deserialize("NonExistentClass"));
+    }
+
+    @Test
+    void testToString() {
+        TypedReg reg = new TypedReg() {};
+        String str = reg.toString();
+        assertTrue(str.contains("regType"));
+        assertTrue(str.contains("keyType"));
+        assertTrue(str.contains("valType"));
     }
 }