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));