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

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


The following commit(s) were added to refs/heads/v3 by this push:
     new a67d5deb3f7 CAUSEWAY-2297: ditch all the custom ObjectSpec caches
a67d5deb3f7 is described below

commit a67d5deb3f7b8ddf5a39e6d9501877a0cf421df4
Author: Andi Huber <ahu...@apache.org>
AuthorDate: Tue Nov 12 19:13:47 2024 +0100

    CAUSEWAY-2297: ditch all the custom ObjectSpec caches
---
 .../collections/snapshot/_VersionedList.java       | 209 ---------------------
 .../collections/snapshot/VersionedListTest.java    |  67 -------
 .../metamodel/specloader/SpecificationCache.java   | 108 -----------
 .../specloader/SpecificationLoaderDefault.java     |  22 ++-
 .../core/metamodel/specloader/_LogUtil.java        |  11 +-
 .../specloader/SpecificationCacheDefaultTest.java  | 109 -----------
 6 files changed, 20 insertions(+), 506 deletions(-)

diff --git 
a/commons/src/main/java/org/apache/causeway/commons/internal/collections/snapshot/_VersionedList.java
 
b/commons/src/main/java/org/apache/causeway/commons/internal/collections/snapshot/_VersionedList.java
deleted file mode 100644
index 84444b7c444..00000000000
--- 
a/commons/src/main/java/org/apache/causeway/commons/internal/collections/snapshot/_VersionedList.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- *  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.collections.snapshot;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-
-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
-import lombok.NonNull;
-
-/**
- * Thread-safe pseudo list, that increments its version each time a snapshot 
is requested.
- * <p>
- * This allows to easily keep track of any additions to the list that occurred 
in between
- * snapshots.
- *
- * @since 2.0
- * @param <T>
- */
-public final class _VersionedList<T> {
-
-    private UUID uuid = UUID.randomUUID();
-    private final List<List<T>> versions = new ArrayList<>();
-    private List<T> currentVersion = new ArrayList<>();
-    private int size;
-
-    @AllArgsConstructor(access = AccessLevel.PRIVATE)
-    public static final class Snapshot<T> {
-        private UUID ownerUuid;
-        @SuppressWarnings("unused")
-        private final int fromIndex; //low endpoint (inclusive) of the copy
-        private final int toIndex; // high endpoint (exclusive) of the copy
-        private final List<List<T>> versions;
-
-        public boolean isEmpty() {
-            return versions.isEmpty();
-        }
-
-        public Stream<T> stream() {
-            return versions.stream()
-                    .flatMap(List::stream);
-        }
-
-        public void forEach(final Consumer<T> action) {
-            for(var ver : versions) {
-                for(var element : ver) {
-                    action.accept(element);
-                }
-            }
-        }
-
-        public void forEachParallel(final Consumer<T> action) {
-            for(var ver : versions) {
-                if(ver.size()>8) {
-                    ver.parallelStream().forEach(action);
-                } else {
-                    for(var element : ver) {
-                        action.accept(element);
-                    }
-                }
-            }
-        }
-
-    }
-
-    public Snapshot<T> snapshot() {
-        synchronized(versions) {
-            commit();
-            return new Snapshot<>(uuid, 0, versions.size(), defensiveCopy());
-        }
-    }
-
-    public Snapshot<T> deltaSince(final @NonNull Snapshot<T> snapshot) {
-
-        if(snapshot.ownerUuid!=uuid) {
-            throw new IllegalArgumentException("Snapshot's UUID is different 
from the VersionedList's.");
-        }
-
-        synchronized(versions) {
-            commit();
-            var from = snapshot.toIndex;
-            var to = versions.size();
-            return new Snapshot<>(uuid, from, to, defensiveCopy(from, to));
-        }
-    }
-
-    public int size() {
-        return size;
-    }
-
-    public boolean isEmpty() {
-        return size==0;
-    }
-
-    /** current implementation cannot handle concurrent additions that occur 
during traversal*/
-    public Stream<T> stream() {
-        final List<List<T>> defensiveCopy;
-        synchronized(versions) {
-            commit();
-            defensiveCopy = defensiveCopy();
-        }
-        return defensiveCopy.stream()
-                .flatMap(List::stream);
-    }
-
-    public boolean add(final T e) {
-        synchronized(versions) {
-            ++size;
-            return currentVersion.add(e);
-        }
-    }
-
-    public boolean addAll(final Collection<? extends T> c) {
-        synchronized(versions) {
-            size+=c.size();
-            return currentVersion.addAll(c);
-        }
-    }
-
-    public void clear() {
-        synchronized(versions) {
-            uuid = UUID.randomUUID();
-            size=0;
-            versions.clear();
-            currentVersion.clear();
-        }
-    }
-
-    /**
-     * Also handles concurrent additions that occur during traversal.
-     * @param action
-     */
-    public void forEach(final Consumer<T> action) {
-        var snapshot = snapshot();
-        snapshot.forEach(action);
-        Snapshot<T> delta = deltaSince(snapshot);
-        while(!delta.isEmpty()) {
-            delta.forEach(action);
-            delta = deltaSince(delta);
-        }
-    }
-
-    /**
-     * Also handles concurrent additions that occur during traversal.
-     * @param action
-     */
-    public void forEachConcurrent(final Consumer<T> action) {
-        var snapshot = snapshot();
-        snapshot.forEachParallel(action);
-        Snapshot<T> delta = deltaSince(snapshot);
-        while(!delta.isEmpty()) {
-            delta.forEachParallel(action);
-            delta = deltaSince(delta);
-        }
-    }
-
-    // -- HELPER
-
-    /**
-     * @implNote only call within synchronized block!
-     * @param fromIndex low endpoint (inclusive) of the copy
-     * @param toIndex high endpoint (exclusive) of the copy
-     */
-    private List<List<T>> defensiveCopy(final int fromIndex, final int 
toIndex) {
-        if(fromIndex==toIndex) {
-            return Collections.emptyList();
-        }
-        return new ArrayList<>(versions.subList(fromIndex, toIndex));
-    }
-
-    /** @implNote only call within synchronized block! */
-    private List<List<T>> defensiveCopy() {
-        if(versions.isEmpty()) {
-            return Collections.emptyList();
-        }
-        return new ArrayList<>(versions);
-    }
-
-    /** @implNote only call within synchronized block! */
-    private void commit() {
-        if(!currentVersion.isEmpty()) {
-            versions.add(currentVersion);
-            currentVersion = new ArrayList<>(); // create a new array for 
others to write to next
-        }
-    }
-
-}
diff --git 
a/commons/src/test/java/org/apache/causeway/commons/internal/collections/snapshot/VersionedListTest.java
 
b/commons/src/test/java/org/apache/causeway/commons/internal/collections/snapshot/VersionedListTest.java
deleted file mode 100644
index 3e473b9a8a5..00000000000
--- 
a/commons/src/test/java/org/apache/causeway/commons/internal/collections/snapshot/VersionedListTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- *  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.collections.snapshot;
-
-import java.util.stream.Collectors;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-class VersionedListTest {
-
-    @Test
-    void test() {
-        
-        var vList = new _VersionedList<String>();
-        
-        assertEquals(0, vList.size());
-        assertTrue(vList.isEmpty());
-        
-        vList.add("foo");
-        
-        assertEquals(1, vList.size());
-        assertFalse(vList.isEmpty());
-        
-        var snapshot1 = vList.snapshot();
-        assertEquals(1, snapshot1.stream().count());
-        
-        vList.add("bar");
-        
-        var snapshot2 = vList.snapshot();
-        assertEquals(2, snapshot2.stream().count());
-        assertEquals("foo,bar", 
snapshot2.stream().collect(Collectors.joining(",")));
-        
-        var delta = vList.deltaSince(snapshot1);
-        assertEquals(1, delta.stream().count());
-        assertEquals("bar", delta.stream().collect(Collectors.joining(",")));
-        
-        vList.add("gru");
-        
-        var snapshot3 = vList.snapshot();
-        assertEquals("foo,bar,gru", 
snapshot3.stream().collect(Collectors.joining(",")));
-        
-        var snapshot4 = vList.deltaSince(snapshot3);
-        assertTrue(snapshot4.isEmpty());
-        
-    }
-
-}
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/SpecificationCache.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/SpecificationCache.java
deleted file mode 100644
index 69ba7063a45..00000000000
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/SpecificationCache.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- *  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.core.metamodel.specloader;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import org.springframework.lang.Nullable;
-
-import org.apache.causeway.commons.collections.Can;
-import 
org.apache.causeway.commons.internal.collections.snapshot._VersionedList;
-import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
-
-import lombok.NonNull;
-
-record SpecificationCache(
-        Map<Class<?>, ObjectSpecification> specByClass,
-        // optimization: specialized list to keep track of any additions to 
the cache fast
-        _VersionedList<ObjectSpecification> vList) {
-
-    SpecificationCache() {
-        this(new HashMap<>(), new _VersionedList<>());
-    }
-
-    Optional<ObjectSpecification> lookup(final Class<?> cls) {
-        synchronized(this) {
-            return Optional.ofNullable(specByClass.get(cls));
-        }
-    }
-
-    ObjectSpecification computeIfAbsent(
-            final Class<?> cls,
-            final Function<Class<?>, ObjectSpecification> mappingFunction) {
-        synchronized(this) {
-            var spec = specByClass.get(cls);
-            if(spec==null) {
-                spec = mappingFunction.apply(cls);
-                internalPut(spec);
-            }
-            return spec;
-        }
-    }
-
-    void clear() {
-        synchronized(this) {
-            specByClass.clear();
-            vList.clear();
-        }
-    }
-
-    /** @returns thread-safe defensive copy */
-    Can<ObjectSpecification> snapshotSpecs() {
-        synchronized(this) {
-            return Can.ofCollection(specByClass.values());
-        }
-    }
-
-    ObjectSpecification remove(@NonNull final Class<?> cls) {
-        synchronized(this) {
-            var removed = specByClass.remove(cls);
-            if(removed!=null) {
-                vList.clear(); // invalidate
-                vList.addAll(specByClass.values());
-            }
-            return removed;
-        }
-    }
-
-    void forEachConcurrent(final Consumer<ObjectSpecification> onSpec) {
-        vList.forEachConcurrent(onSpec);
-    }
-
-    void forEach(final Consumer<ObjectSpecification> onSpec) {
-        vList.forEach(onSpec);
-    }
-
-    // -- HELPER
-
-    private void internalPut(@Nullable final ObjectSpecification spec) {
-        if(spec==null) return;
-
-        var cls = spec.getCorrespondingClass();
-        var existing = specByClass.put(cls, spec);
-        if(existing==null) {
-            vList.add(spec); // add to vList only if we don't have it already
-        }
-    }
-
-}
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/SpecificationLoaderDefault.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/SpecificationLoaderDefault.java
index 15acdf02974..7a44e5fa4be 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/SpecificationLoaderDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/SpecificationLoaderDefault.java
@@ -21,8 +21,10 @@ package org.apache.causeway.core.metamodel.specloader;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -132,7 +134,7 @@ implements
 
     private FacetProcessor facetProcessor;
 
-    private final SpecificationCache cache = new SpecificationCache();
+    private final Map<Class<?>, ObjectSpecification> cache = new 
ConcurrentHashMap<>();
     private final LogicalTypeResolver logicalTypeResolver = new 
LogicalTypeResolver();
 
     /**
@@ -287,7 +289,7 @@ implements
         //XXX[CAUSEWAY-2382] when parallel introspecting, make sure we have 
the mixins before their holders
         // (observation by experiment, no real understanding as to why)
 
-        _LogUtil.logBefore(log, cache, knownSpecs);
+        _LogUtil.logBefore(log, this::snapshotSpecifications, knownSpecs);
 
         log.info(" - introspecting {} type hierarchies", knownSpecs.size());
         introspect(Can.ofCollection(knownSpecs), 
IntrospectionState.TYPE_INTROSPECTED);
@@ -311,10 +313,10 @@ implements
 
         introspect(Can.ofCollection(domainObjectSpecs), 
IntrospectionState.FULLY_INTROSPECTED);
 
-        _LogUtil.logAfter(log, cache, knownSpecs);
+        _LogUtil.logAfter(log, this::snapshotSpecifications, knownSpecs);
 
         if(isFullIntrospect()) {
-            var snapshot = cache.snapshotSpecs();
+            var snapshot = snapshotSpecifications();
             log.info(" - introspecting all {} types eagerly 
(FullIntrospect=true)", snapshot.size());
             introspect(snapshot.filter(x->x.getBeanSort().isMixin()), 
IntrospectionState.FULLY_INTROSPECTED);
             introspect(snapshot.filter(x->!x.getBeanSort().isMixin()), 
IntrospectionState.FULLY_INTROSPECTED);
@@ -452,18 +454,22 @@ implements
 
     @Override
     public Can<ObjectSpecification> snapshotSpecifications() {
-        return cache.snapshotSpecs();
+        return Can.ofCollection(cache.values());
     }
 
     @Override
     public void forEach(final Consumer<ObjectSpecification> onSpec) {
         var shouldRunConcurrent = 
causewayConfiguration.getCore().getMetaModel().getValidator().isParallelize();
+        var snapshot = snapshotSpecifications();
         if(shouldRunConcurrent) {
-            cache.forEachConcurrent(onSpec);
+            snapshot
+                .stream()
+                .parallel()
+                .forEach(onSpec);
         } else {
-            cache.forEach(onSpec);
+            snapshot
+                .forEach(onSpec);
         }
-
     }
 
     @Override
diff --git 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/_LogUtil.java
 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/_LogUtil.java
index 041a2df6025..c521cc0e4de 100644
--- 
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/_LogUtil.java
+++ 
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/_LogUtil.java
@@ -20,10 +20,12 @@ package org.apache.causeway.core.metamodel.specloader;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import org.apache.logging.log4j.Logger;
 
+import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 
 import lombok.experimental.UtilityClass;
@@ -33,15 +35,14 @@ final class _LogUtil {
 
     void logBefore(
             final Logger log,
-            final SpecificationCache cache,
+            final Supplier<Can<ObjectSpecification>> snapshot,
             final List<? extends ObjectSpecification> scanned) {
 
         if(!log.isDebugEnabled()) {
             return;
         }
 
-        var cached = cache.snapshotSpecs();
-
+        var cached = snapshot.get();
         log.debug(String.format(
                 "scanned.size = %d ; cached.size = %d",
                 scanned.size(), cached.size()));
@@ -60,14 +61,14 @@ final class _LogUtil {
 
     void logAfter(
             final Logger log,
-            final SpecificationCache cache,
+            final Supplier<Can<ObjectSpecification>> snapshot,
             final Collection<? extends ObjectSpecification> scanned) {
 
         if(!log.isDebugEnabled()) {
             return;
         }
 
-        var cached = cache.snapshotSpecs();
+        var cached = snapshot.get();
         var cachedAfterNotBefore = cached.stream()
                 .filter(spec -> !scanned.contains(spec))
                 .collect(Collectors.toList());
diff --git 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/SpecificationCacheDefaultTest.java
 
b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/SpecificationCacheDefaultTest.java
deleted file mode 100644
index f129b7aaf37..00000000000
--- 
a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/SpecificationCacheDefaultTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- *  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.core.metamodel.specloader;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertSame;
-
-import org.apache.causeway.applib.id.LogicalType;
-import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
-
-class SpecificationCacheDefaultTest {
-
-    private LogicalType cus = _LogicalTypeTestFactory.cus();
-    private LogicalType ord = _LogicalTypeTestFactory.ord();
-
-    ObjectSpecification customerSpec;
-    ObjectSpecification orderSpec;
-
-    private SpecificationCache specificationCache = new SpecificationCache();
-    private LogicalTypeResolver logicalTypeResolver = new 
LogicalTypeResolver();
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @BeforeEach
-    public void setUp() throws Exception {
-
-        customerSpec = Mockito.mock(ObjectSpecification.class);
-        orderSpec = Mockito.mock(ObjectSpecification.class);
-
-        
Mockito.when(customerSpec.getCorrespondingClass()).thenReturn((Class)Customer.class);
-        Mockito.when(customerSpec.getLogicalType()).thenReturn(cus);
-
-        
Mockito.when(orderSpec.getCorrespondingClass()).thenReturn((Class)Order.class);
-        Mockito.when(orderSpec.getLogicalType()).thenReturn(ord);
-    }
-
-    @AfterEach
-    public void tearDown() throws Exception {
-        specificationCache = null;
-    }
-
-    static class Customer {}
-    static class Order {}
-
-    @Test
-    public void get_whenNotCached() {
-        assertFalse(specificationCache.lookup(Customer.class).isPresent());
-    }
-
-    @Test
-    public void get_whenCached() {
-
-        specificationCache.computeIfAbsent(Customer.class, __->customerSpec);
-
-        final ObjectSpecification objectSpecification = 
specificationCache.lookup(Customer.class)
-                .orElse(null);
-
-        assertSame(objectSpecification, customerSpec);
-    }
-
-    @Test
-    public void allSpecs_whenCached() {
-        specificationCache.computeIfAbsent(Customer.class, __->customerSpec);
-        specificationCache.computeIfAbsent(Order.class, __->orderSpec);
-
-        var allSpecs = specificationCache.snapshotSpecs();
-
-        assertThat(allSpecs.size(), is(2));
-    }
-
-    @Test
-    public void getByObjectType_whenNotSet() {
-        var type = logicalTypeResolver.lookup(cus.logicalName());
-        assertFalse(type.isPresent());
-    }
-
-    @Test
-    public void getByObjectType_whenSet() {
-
-        specificationCache.computeIfAbsent(Customer.class, __->customerSpec);
-
-        var objectSpec = 
specificationCache.lookup(Customer.class).orElse(null);
-
-        assertSame(objectSpec, customerSpec);
-    }
-
-}

Reply via email to