This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v4
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 0fe7edc2929f5ca22bfa264752c158186540ab3f
Author: Andi Huber <[email protected]>
AuthorDate: Fri Sep 26 09:18:35 2025 +0200

    CAUSEWAY-3905: flesh out _StableValue object contracts
---
 .../commons/internal/base/_StableValue.java        | 46 ++++++++++++++---
 .../commons/internal/base/StableValueTest.java     | 57 ++++++++++++++++++++++
 2 files changed, 97 insertions(+), 6 deletions(-)

diff --git 
a/commons/src/main/java/org/apache/causeway/commons/internal/base/_StableValue.java
 
b/commons/src/main/java/org/apache/causeway/commons/internal/base/_StableValue.java
index 2f46a325252..1f97402a992 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/internal/base/_StableValue.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/internal/base/_StableValue.java
@@ -18,7 +18,10 @@
  */
 package org.apache.causeway.commons.internal.base;
 
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
 import java.io.Serializable;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
 
@@ -26,13 +29,17 @@
  * <h1>- internal use only -</h1>
  * <p><b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this 
package! <br/>
  * These may be changed or removed without notice!
- * <p>
- * A thread-safe, lazily initialized holder that can be set at most once.
- * <p>
- * This class mimics the behavior of the StableValue API introduced in JDK 25 
preview.
+ *
+ * <p>A thread-safe, lazily initialized holder that can be set at most once.
+ *
+ * <p>This class mimics the behavior of the StableValue API introduced in JDK 
25 preview.
  * It ensures that a value is set only once, using a supplier, and subsequent 
calls return the same value.
  * Useful for caching expensive computations or initializing resources lazily 
and safely.
  *
+ * <p>_StableValue is serializable, but such that we don't marshall the 
contained value, if any.
+ *
+ * <p>Equality: 2 _StableValue(s) are equal iff their contained values are 
equal.
+ *
  * @param <T> the type of the value to be held
  * @since 4.0
  */
@@ -42,7 +49,7 @@ public _StableValue() {
         this(new AtomicReference<>());
     }
 
-    public _StableValue(T t) {
+    public _StableValue(final T t) {
         this(new AtomicReference<>(t));
     }
 
@@ -54,7 +61,7 @@ public _StableValue(T t) {
      * @param supplier the supplier to initialize the value
      * @return the stored value
      */
-    public T orElseSet(Supplier<T> supplier) {
+    public T orElseSet(final Supplier<T> supplier) {
         T value = ref.get();
         if (value == null) {
             T newValue = supplier.get();
@@ -91,4 +98,31 @@ public T get() {
         return value;
     }
 
+    // -- CONTRACT
+
+    @Override
+    public final boolean equals(final Object arg0) {
+        return arg0 instanceof _StableValue other
+            ? Objects.equals(this.ref.get(), other.ref.get())
+            : false;
+    }
+
+    // -- SERIALIZATION PROXY
+
+    private final static class StableValueProxy implements Serializable {
+        private static final long serialVersionUID = 1L;
+        StableValueProxy() {}
+        private Object readResolve() {
+            return new _StableValue<>();
+        }
+    }
+
+    private Object writeReplace() {
+        return new StableValueProxy();
+    }
+
+    private void readObject(final ObjectInputStream stream) throws 
InvalidObjectException {
+        throw new InvalidObjectException("Proxy required");
+    }
+
 }
\ No newline at end of file
diff --git 
a/commons/src/test/java/org/apache/causeway/commons/internal/base/StableValueTest.java
 
b/commons/src/test/java/org/apache/causeway/commons/internal/base/StableValueTest.java
new file mode 100644
index 00000000000..204e9843b7c
--- /dev/null
+++ 
b/commons/src/test/java/org/apache/causeway/commons/internal/base/StableValueTest.java
@@ -0,0 +1,57 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.commons.internal.base;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.apache.causeway.commons.internal.testing._SerializationTester;
+
+class StableValueTest {
+
+    @Test
+    void equality_whenEmpty() {
+        assertEquals(new _StableValue<>(), new _StableValue<>());
+    }
+
+    @Test
+    void equality_whenNotEmpty() {
+        assertEquals(new _StableValue<>("test"), new _StableValue<>("test"));
+    }
+
+    @Test
+    void nonEquality_whenDiffer() {
+        assertNotEquals(new _StableValue<>("a"), new _StableValue<>("b"));
+    }
+
+    @Test
+    void shouldRountrip_whenEmpty() {
+        _SerializationTester.assertEqualsOnRoundtrip(new _StableValue<>());
+    }
+
+    @Test
+    void shouldBeEmpty_whenRoundtrip() {
+        assertEquals(
+            new _StableValue<>(),
+            _SerializationTester.roundtrip(new _StableValue<String>("test")));
+    }
+
+}

Reply via email to