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

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


The following commit(s) were added to refs/heads/main by this push:
     new be1559e193f CAUSEWAY-3937: refactors ConcurrentMapWrapper into a util
be1559e193f is described below

commit be1559e193f830c8719622419193d64041e069b7
Author: andi-huber <[email protected]>
AuthorDate: Mon Jan 26 07:56:12 2026 +0100

    CAUSEWAY-3937: refactors ConcurrentMapWrapper into a util
---
 .../commons/internal/collections/_Maps.java        | 76 +++++++++++++++-------
 .../core/metamodel/services/grid/GridCache.java    | 51 +--------------
 .../tabular/simple/DataTableSerializationTest.java |  4 +-
 3 files changed, 55 insertions(+), 76 deletions(-)

diff --git 
a/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Maps.java
 
b/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Maps.java
index 43fe370656b..0eb7245412c 100644
--- 
a/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Maps.java
+++ 
b/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Maps.java
@@ -25,6 +25,7 @@
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
@@ -120,9 +121,8 @@ public static <K, V> Map<K, V> unmodifiable(final K k1, 
final V v1, final K k2,
 
     @SafeVarargs
     public static <K, V> Map<K, V> unmodifiableEntries(final Map.@NonNull 
Entry<? extends K,? extends V>... entries) {
-        if(entries.length==0) {
+        if(entries.length==0)
             return Collections.emptyMap();
-        }
 
         final LinkedHashMap<K, V> mapPreservingOrder = newLinkedHashMap();
 
@@ -133,7 +133,7 @@ public static <K, V> Map<K, V> unmodifiableEntries(final 
Map.@NonNull Entry<? ex
     }
 
     public static <K, V> Map.Entry<K, V> entry(final K k, final V v){
-        return new AbstractMap.SimpleEntry<K, V>(k, v);
+        return new AbstractMap.SimpleEntry<>(k, v);
     }
 
     // -- TO STRING
@@ -182,9 +182,8 @@ public static <K, V0, V1> Map<K, V1> mapValues(
         var resultMap = mapFactory.get();
 
         if(input==null
-                || input.isEmpty()) {
+                || input.isEmpty())
             return resultMap;
-        }
 
         input.forEach((k, v)->resultMap.put(k, valueMapper.apply(v)));
         return resultMap;
@@ -197,9 +196,8 @@ public static <K, V> Map<K, V> filterKeys(
 
         final Map<K, V> result = factory.get();
 
-        if(input==null) {
+        if(input==null)
             return result;
-        }
 
         input.forEach((k, v)->{
             if(keyFilter.test(k)) {
@@ -212,9 +210,8 @@ public static <K, V> Map<K, V> filterKeys(
 
     public static <K, V> ListMultimap<V, K> invertToListMultimap(final Map<K, 
V> input) {
         final ListMultimap<V, K> result = _Multimaps.newListMultimap();
-        if(input==null) {
+        if(input==null)
             return result;
-        }
         input.forEach((k, v)->result.putElement(v, k));
         return result;
     }
@@ -224,29 +221,29 @@ public static <K, V> ListMultimap<V, K> 
invertToListMultimap(final Map<K, V> inp
     // -- HASH MAP
 
     public static <K, V> HashMap<K, V> newHashMap() {
-        return new HashMap<K, V>();
+        return new HashMap<>();
     }
 
     // -- LINKED HASH MAP
 
     public static <K, V> LinkedHashMap<K, V> newLinkedHashMap() {
-        return new LinkedHashMap<K, V>();
+        return new LinkedHashMap<>();
     }
 
     // -- CONCURRENT HASH MAP
 
     public static <K, V> ConcurrentHashMap<K, V> newConcurrentHashMap() {
-        return new ConcurrentHashMap<K, V>();
+        return new ConcurrentHashMap<>();
     }
 
     // -- TREE MAP
 
     public static <K, V> TreeMap<K, V> newTreeMap() {
-        return new TreeMap<K, V>();
+        return new TreeMap<>();
     }
 
     public static <K, V> TreeMap<K, V> newTreeMap(final Comparator<? super K> 
comparator) {
-        return new TreeMap<K, V>(comparator);
+        return new TreeMap<>(comparator);
     }
 
     // -- ALIAS MAP
@@ -259,7 +256,7 @@ private record KeyPair<K>(
     public static <K, V> AliasMap<K, V> newAliasMap(
             final @NonNull Supplier<Map<K, V>> mapFactory){
 
-        return new AliasMap<K, V>() {
+        return new AliasMap<>() {
 
             final Map<K, V> delegate = mapFactory.get();
 
@@ -278,7 +275,7 @@ public V put(final K key, final V value) {
             @Override
             public void putAll(final Map<? extends K, ? extends V> other) {
                 if(!_NullSafe.isEmpty(other)) {
-                    other.forEach((k, v)->this.put(k, v));
+                    other.forEach(this::put);
                 }
             }
 
@@ -303,9 +300,8 @@ public boolean containsKey(final Object keyOrAliasKey) {
             @Override
             public V get(final Object keyOrAliasKey) {
                 var v = delegate.get(keyOrAliasKey);
-                if(v!=null) {
+                if(v!=null)
                     return v;
-                }
                 return getByAliasKey(keyOrAliasKey);
             }
 
@@ -331,21 +327,18 @@ private void putAliasKeys(final K key, final Can<K> 
aliasKeys, final boolean rem
                     for(var aliasKey : aliasKeys) {
 
                         var existingKeyPair = pairByAliasKey.put(aliasKey, 
keyPair);
-                        if(existingKeyPair!=null && !remap) {
-
+                        if(existingKeyPair!=null && !remap)
                             throw _Exceptions.illegalArgument(
                                     "alias key collision on alias %s: 
existing-key=%s, new-key=%s",
                                     aliasKey, existingKeyPair.key, 
keyPair.key);
-                        }
                     }
                 }
             }
 
             private V getByAliasKey(final Object aliasKey) {
                 var keyPair = pairByAliasKey.get(aliasKey);
-                if(keyPair!=null) {
+                if(keyPair!=null)
                     return delegate.get(keyPair.key());
-                }
                 return null;
             }
 
@@ -370,6 +363,41 @@ private void clearAliasKeys() {
         };
     }
 
-    // --
+    // -- UTILITY
+
+    /**
+     * Provides a workaround when {@link 
ConcurrentHashMap#computeIfAbsent(Object, Function)}, is not applicable due
+     * to recursive update attempts, which are not allowed.
+     *
+     * <p>Applicable when the mapping-function is a {@literal pure} function.
+     */
+    public record ConcurrentMapWrapper<K, V>(ConcurrentHashMap<K, V> map) {
+
+        public ConcurrentMapWrapper() {
+            this(new ConcurrentHashMap<>());
+        }
+
+        /**
+         * Applicable when the mappingFunction is a {@literal pure} function. 
Particularly useful in caching scenarios,
+         * where the mappingFunction potentially modifies the {@link 
ConcurrentHashMap} of this wrapper.
+         *
+         * @implNote Implementation is same as the default implementation from 
the {@link Map} interface. But,
+         *      {@link ConcurrentHashMap} overrides this default, in order to 
detect when the mappingFunction
+         *      attempts a recursive update, then throws an {@link 
IllegalStateException}.
+         */
+        public V computeIfAbsent(final K key, final 
java.util.function.Function<? super K, ? extends V> mappingFunction) {
+            Objects.requireNonNull(mappingFunction);
+
+            var value = map.get(key);
+            if(value!=null)
+                return value;
+
+            var newValue = mappingFunction.apply(key);
+            if(newValue!=null) {
+                map.put(key, newValue);
+            }
+            return newValue;
+        }
+    }
 
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
index 28e65eec99f..676b28a93c3 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
@@ -24,6 +24,7 @@
 import org.apache.causeway.applib.layout.grid.bootstrap.BSGrid;
 import org.apache.causeway.applib.services.grid.GridService.LayoutKey;
 import org.apache.causeway.commons.functional.Try;
+import 
org.apache.causeway.commons.internal.collections._Maps.ConcurrentMapWrapper;
 
 import lombok.extern.slf4j.Slf4j;
 
@@ -51,54 +52,4 @@ public Try<BSGrid> computeIfAbsent(final LayoutKey 
layoutKey, final Function<Lay
         return gridsByKey.computeIfAbsent(layoutKey, factory);
     }
 
-    /* when using ConcurrentHashMap directly we may see
-    java.lang.IllegalStateException: Recursive update
-    at 
java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1779)
-    at 
org.apache.causeway.core.metamodel.services.grid.GridCache.computeIfAbsent(GridCache.java:52)
-    at 
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.load(GridServiceDefault.java:95)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.load(BSGridFacet.java:117)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.lambda$normalized$0(BSGridFacet.java:86)
-    at 
java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1932)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.normalized(BSGridFacet.java:82)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.getGrid(BSGridFacet.java:68)
-    at 
org.apache.causeway.core.metamodel.util.Facets.lambda$gridPreload$0(Facets.java:204)
-    at java.base/java.util.Optional.ifPresent(Optional.java:178)
-    at 
org.apache.causeway.core.metamodel.util.Facets.gridPreload(Facets.java:200)
-    at 
org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.introspectFully(ObjectSpecificationDefault.java:640)
-    at 
org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.introspectUpTo(ObjectSpecificationDefault.java:615)
-    at 
org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.streamDeclaredAssociations(ObjectSpecificationDefault.java:1000)
-    at 
org.apache.causeway.core.metamodel.spec.impl.ObjectMemberContainer.streamAssociations(ObjectMemberContainer.java:127)
-    at 
org.apache.causeway.core.metamodel.spec.impl.ObjectMemberContainer.streamAssociations(ObjectMemberContainer.java:128)
-    at 
org.apache.causeway.core.metamodel.spec.feature.ObjectAssociationContainer.streamProperties(ObjectAssociationContainer.java:118)
-    at 
org.apache.causeway.core.metamodel.services.grid.ObjectMemberResolverForGrid.validateAndNormalize(ObjectMemberResolverForGrid.java:148)
-    at 
org.apache.causeway.core.metamodel.services.grid.ObjectMemberResolverForGrid.resolve(ObjectMemberResolverForGrid.java:115)
-    at 
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.lambda$tryLoadNoCache$7(GridServiceDefault.java:110)
-    at java.base/java.util.Optional.orElseGet(Optional.java:364)
-    at 
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.lambda$tryLoadNoCache$6(GridServiceDefault.java:110)
-    at 
org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63)
-    at 
org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51)
-    ..
-    at 
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.tryLoadNoCache(GridServiceDefault.java:110)
-    at 
java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1724)
-    at 
org.apache.causeway.core.metamodel.services.grid.GridCache.computeIfAbsent(GridCache.java:52)
-    at 
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.load(GridServiceDefault.java:95)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.load(BSGridFacet.java:117)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.lambda$normalized$0(BSGridFacet.java:86)
-    at 
java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1932)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.normalized(BSGridFacet.java:82)
-    at 
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.getGrid(BSGridFacet.java:68)
-    ..
-    at 
org.apache.causeway.core.metamodel.util.Facets.gridPreload(Facets.java:200)*/
-    record ConcurrentMapWrapper<K, V>(ConcurrentHashMap<K, V> map) {
-        public V computeIfAbsent(final K key, final 
java.util.function.Function<? super K, ? extends V> mappingFunction) {
-            var value = map.get(key);
-            if(value!=null)
-                return value;
-
-            var newValue = mappingFunction.apply(key);
-            map.put(key, newValue);
-            return newValue;
-        }
-    }
-
 }
diff --git 
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
 
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
index 09e6e6c8da4..65eeed12a7e 100644
--- 
a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
+++ 
b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/tabular/simple/DataTableSerializationTest.java
@@ -69,7 +69,7 @@ public record CustomerRecord(@Property String memento) 
implements ViewModel {
     @ParameterizedTest
     @ValueSource(classes = {CustomerClass.class, CustomerRecord.class})
     void roundtripOnEmptyTable(final Class<? extends ViewModel> 
viewmodelClass) {
-        var original = DataTable.forDomainType(viewmodelClass); //FIXME may 
throw IllegalState Recursive update
+        var original = DataTable.forDomainType(viewmodelClass);
         var afterRoundtrip = _SerializationTester.roundtrip(original);
 
         assertNotNull(afterRoundtrip);
@@ -84,7 +84,7 @@ void roundtripOnEmptyTable(final Class<? extends ViewModel> 
viewmodelClass) {
     @ParameterizedTest
     @ValueSource(classes = {CustomerClass.class, CustomerRecord.class})
     void roundtripOnPopulatedTable(final Class<? extends ViewModel> 
viewmodelClass) {
-        var original = DataTable.forDomainType(viewmodelClass) //FIXME may 
throw IllegalState Recursive update
+        var original = DataTable.forDomainType(viewmodelClass)
             .withDataElementPojos(Can.of("cus-1", "cus-2")
                 .map(name->newInstance(viewmodelClass, name))
                 .map(getObjectManager()::adapt));

Reply via email to