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

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

commit b8b5864a050a2150bbd880d44744b55c9f4c0a07
Author: Andy Seaborne <[email protected]>
AuthorDate: Fri Jul 18 09:55:32 2025 +0100

    Single-threaded LRU cache based on LinkedHashMap
---
 .../main/java/org/apache/jena/atlas/lib/Cache.java |   2 +-
 .../org/apache/jena/atlas/lib/CacheFactory.java    |   8 +
 .../org/apache/jena/atlas/lib/cache/CacheOps.java  |   6 +-
 .../apache/jena/atlas/lib/cache/CachePlainLRU.java | 122 ++++++++++++++
 .../java/org/apache/jena/atlas/lib/TestCache.java  | 179 ++++++++++-----------
 .../java/org/apache/jena/atlas/lib/TestCache2.java |  25 ++-
 6 files changed, 235 insertions(+), 107 deletions(-)

diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java 
b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
index 46dd53c63c..93437c424f 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
@@ -76,7 +76,7 @@ public interface Cache<Key, Value>
      * @return Returns either the existing value or the calculated value.
      *         If callable is called and returns null, then null is returned.
      */
-    public Value get(Key key, Function<Key, Value> callable) ;
+    public Value get(Key key, Function<Key, Value> function) ;
 
     /**
      * Insert into the cache
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java 
b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
index 7ab9f09f60..6cc8ed9a1f 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
@@ -88,6 +88,14 @@ public class CacheFactory {
         return new Cache1<>() ;
     }
 
+    /*
+     * Create a lightweight LRU cache.
+     * This cache is not thread-safe.
+     */
+    public static <Key, Value> Cache<Key, Value> createPlainCache(int size) {
+        return new CachePlainLRU<>(size) ;
+    }
+
     /**
      * Create set-cache, rather than a map-cache.
      * The cache is thread-safe for single operations.
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java 
b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java
index 4a81269cfa..fe6c001188 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java
@@ -46,11 +46,7 @@ class CacheOps {
     public static <K,V> V getOrFill(Cache<K,V> cache, K key, Function<K,V> 
function) {
         V value = cache.getIfPresent(key) ;
         if ( value == null ) {
-            try { value = function.apply(key) ; }
-            catch (RuntimeException ex) { throw ex; }
-            catch (Exception e) {
-                throw new AtlasException("Exception on cache fill", e) ;
-            }
+            value = function.apply(key) ;
             if ( value != null )
                 cache.put(key, value) ;
         }
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CachePlainLRU.java 
b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CachePlainLRU.java
new file mode 100644
index 0000000000..a6545cf74f
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CachePlainLRU.java
@@ -0,0 +1,122 @@
+/*
+ * 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.jena.atlas.lib.cache;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.apache.jena.atlas.lib.Cache;
+
+/**
+ * Light-weight LRU cache based on {@link LinkedHashMap}.
+ * This cache implementation is for single-threaded use only.
+ */
+public class CachePlainLRU<K, V> implements Cache<K, V> {
+
+    private final Map<K, V> cache;
+    private final float loadFactor = 0.75f;
+    private final float initialCapacityFactory = 0.75f;
+
+
+    public CachePlainLRU(final int maxCapacity) {
+        int initialCapacity = (int)(maxCapacity / initialCapacityFactory + 1);
+        this.cache = new LinkedHashMap<K, V>(initialCapacity, loadFactor, 
true) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+                return size() > maxCapacity;
+            }
+        };
+    }
+
+    @Override
+    public boolean containsKey(K key) {
+        return cache.containsKey(key);
+    }
+
+
+    @Override
+    public V getIfPresent(K key) {
+        return cache.get(key);
+    }
+
+
+    @Override
+    public V get(K key, Function<K, V> function) {
+        return CacheOps.getOrFill(this, key, function);
+    }
+
+    @Override
+    public void put(K key, V thing) {
+        cache.put(key, thing);
+    }
+
+
+    @Override
+    public void remove(K key) {
+        this.remove(key);
+    }
+
+
+    @Override
+    public Iterator<K> keys() {
+        return cache.keySet().iterator();
+    }
+
+
+    @Override
+    public boolean isEmpty() {
+        return cache.isEmpty();
+    }
+
+
+    @Override
+    public void clear() {}
+
+
+    @Override
+    public long size() {
+        return 0;
+    }
+
+
+//
+//    @Override
+//    public boolean containsKey(final K key) {
+//        return cache.containsKey(key);
+//    }
+//
+//    @Override
+//    public V get(final K key) {
+//        return cache.get(key);
+//    }
+//
+//    @Override
+//    public void put(final K key, V value) {
+//        cache.put(key, value);
+//    }
+//
+//    public long size() {
+//        return cache.size();
+//    }
+
+
+
+}
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java 
b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java
index bc1f7a6d94..6458c74f18 100644
--- a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java
@@ -37,114 +37,103 @@ import org.junit.runners.Parameterized.Parameters ;
 public class TestCache
 {
     // Tests do not apply to cache1.
-    private static interface CacheMaker<K,V> { Cache<K,V> make(int size) ; 
String name() ; } 
-
-    private static CacheMaker<Integer, Integer> simple = 
-        new CacheMaker<Integer, Integer>()
-        { 
-        @Override
-        public Cache<Integer, Integer> make(int size) { return 
CacheFactory.createSimpleCache(size) ; }
-        @Override
-        public String name() { return "Simple" ; } 
-        } 
-    ;
-
-    private static CacheMaker<Integer, Integer> standard = 
-        new CacheMaker<Integer, Integer>()
-        {
-        @Override
-        public Cache<Integer, Integer> make(int size) { return 
CacheFactory.createCache(size) ; }
-        @Override
-        public String name() { return "Standard" ; } 
-        }
-    ;
-
-    @Parameters
+    private static interface CacheMaker<K,V> { Cache<K,V> make(int size) ; }
+
+    private static CacheMaker<Integer, Integer> oneSlot = (int 
size)->CacheFactory.createOneSlotCache();
+    private static CacheMaker<Integer, Integer> simple = (int 
size)->CacheFactory.createSimpleCache(size);
+    private static CacheMaker<Integer, Integer> standard = (int 
size)->CacheFactory.createCache(size);
+    private static CacheMaker<Integer, Integer> plainLRU = (int 
size)->CacheFactory.createCache(size);
+
+    @Parameters(name="{0}")
     public static Collection<Object[]> cacheMakers()
     {
-        return Arrays.asList(new Object[][] {
-            { simple , 10 }
-            , { simple , 2 } 
-            , { simple , 1 }
-            , { standard , 10 }
-            , { standard , 2 }
-            , { standard , 1 }
-        } ) ; 
+        return Arrays.asList(new Object[][]
+                { { "Simple(10)", simple , 10 }
+                , { "Simple(2)", simple , 2 }
+                , { "Simple(1)" , simple ,1 }
+                , { "Plain(10)", plainLRU , 10 }
+                , { "Plain(2)", plainLRU , 2 }
+                , { "Plain(1)" , plainLRU ,1 }
+                , { "Standard(10)" , standard, 10 }
+                , { "Standard(2)"  , standard, 2 }
+                , { "Standard(1)"  , standard, 1 }
+                , { "SingleSlot"  , oneSlot, 1 }
+                } ) ;
     }
 
-    Cache<Integer, Integer> cache ;
-    CacheMaker<Integer,Integer> cacheMaker ;
-    int size ;
-    
-    
-    public TestCache(CacheMaker<Integer,Integer> cacheMaker, int size)
-    {
-        this.cacheMaker = cacheMaker ;
-        this.size = size ;
-        
+    Cache<Integer, Integer> cache;
+    CacheMaker<Integer, Integer> cacheMaker;
+    int size;
+
+    public TestCache(String name, CacheMaker<Integer, Integer> cacheMaker, int 
size) {
+        this.cacheMaker = cacheMaker;
+        this.size = size;
     }
-    
-    @Before public void before() { cache = cacheMaker.make(size) ; }
-    
-    @Test public void cache_00()
-    {
-        assertEquals(0, cache.size()) ;
-        assertTrue(cache.isEmpty()) ;
+
+    @Before
+    public void before() {
+        cache = cacheMaker.make(size);
     }
-    
-    @Test public void cache_01()
-    {
-        Integer x = cache.getIfPresent(7) ;
-        cache.put(7, 7) ;
-        assertEquals(1, cache.size()) ;
-        assertNull(x) ;
-        assertTrue(cache.containsKey(7)) ;
-        assertEquals(Integer.valueOf(7), cache.getIfPresent(7)) ;
+
+    @Test
+    public void cache_00() {
+        assertEquals(0, cache.size());
+        assertTrue(cache.isEmpty());
     }
-    
-    @Test public void cache_02()
-    {
-        cache.put(7, 7) ;
-        cache.put(8, 8) ;
+
+    @Test
+    public void cache_01() {
+        Integer x = cache.getIfPresent(7);
+        cache.put(7, 7);
+        assertEquals(1, cache.size());
+        assertNull(x);
+        assertTrue(cache.containsKey(7));
+        assertEquals(Integer.valueOf(7), cache.getIfPresent(7));
+    }
+
+    @Test
+    public void cache_02() {
+        cache.put(7, 7);
+        cache.put(8, 8);
         // Not true for Cache1.
         if ( size > 2 )
-            assertEquals(2, cache.size()) ;
+            assertEquals(2, cache.size());
         if ( size > 2 )
-            assertTrue(cache.containsKey(7)) ;
-        
+            assertTrue(cache.containsKey(7));
+
         if ( size > 2 )
-            assertEquals(Integer.valueOf(7), cache.getIfPresent(7)) ;
-        
-        assertTrue(cache.containsKey(8)) ;
-        assertEquals(Integer.valueOf(8), cache.getIfPresent(8)) ;
+            assertEquals(Integer.valueOf(7), cache.getIfPresent(7));
+
+        assertTrue(cache.containsKey(8));
+        assertEquals(Integer.valueOf(8), cache.getIfPresent(8));
     }
-    
-    @Test public void cache_03()
-    {
-        cache.put(7, 7) ;
-        Integer x1 = cache.getIfPresent(7) ;
-        cache.put(7, 18) ;
-        assertEquals(1, cache.size()) ;
-        assertEquals(7, x1.intValue()) ;
-        assertTrue(cache.containsKey(7)) ;
-        assertEquals(Integer.valueOf(18), cache.getIfPresent(7)) ;
+
+    @Test
+    public void cache_03() {
+        cache.put(7, 7);
+        Integer x1 = cache.getIfPresent(7);
+        cache.put(7, 18);
+        assertEquals(1, cache.size());
+        assertEquals(7, x1.intValue());
+        assertTrue(cache.containsKey(7));
+        assertEquals(Integer.valueOf(18), cache.getIfPresent(7));
     }
-    
-    @Test public void cache_04()
-    {
-        cache.clear() ;
-        cache.put(7, 77) ;
-        List<Integer> x = Iter.toList(cache.keys()) ;
-        assertEquals(1, x.size()) ;
-        assertEquals(Integer.valueOf(7), x.get(0)) ;
+
+    @Test
+    public void cache_04() {
+        cache.clear();
+        cache.put(7, 77);
+        List<Integer> x = Iter.toList(cache.keys());
+        assertEquals(1, x.size());
+        assertEquals(Integer.valueOf(7), x.get(0));
     }
-    
-    @Test public void cache_05()
-    {
-        cache.clear() ;
-        cache.put(7, 77) ;
-        cache.clear() ;
-        assertEquals(0, cache.size()) ; 
-        assertTrue(cache.isEmpty()) ;
+
+    @Test
+    public void cache_05() {
+        cache.clear();
+        cache.put(7, 77);
+        cache.clear();
+        assertEquals(0, cache.size());
+        assertTrue(cache.isEmpty());
     }
 }
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java 
b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java
index f66294016a..cc333fad95 100644
--- a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java
@@ -25,9 +25,8 @@ import org.junit.Test;
 
 // Non-parameterized tests
 public class TestCache2 {
-    // Cache1
     @Test
-    public void cache_10() {
+    public void cache_oneSlot() {
         Cache<Integer, String> cache = CacheFactory.createOneSlotCache();
         String str = cache.getIfPresent(1);
         assertNull(str);
@@ -51,7 +50,7 @@ public class TestCache2 {
     @Test
     public void cacheGetter_1() {
         Cache<Integer, String> cache = CacheFactory.createCache(2);
-        String str = cache.get(1, k->k.toString());
+        String str = cache.get(1, k -> k.toString());
         assertEquals("1", str);
     }
 
@@ -59,9 +58,9 @@ public class TestCache2 {
     @Test
     public void cacheGetter_2() {
         Cache<Integer, String> cache = CacheFactory.createCache(2);
-        String str1 = cache.get(1, k->k.toString());
-        String str2 = cache.get(2, k->k.toString());
-        String str3 = cache.get(3, k->k.toString());
+        String str1 = cache.get(1, k -> k.toString());
+        String str2 = cache.get(2, k -> k.toString());
+        String str3 = cache.get(3, k -> k.toString());
         assertEquals("1", str1);
         assertEquals("2", str2);
         assertEquals("3", str3);
@@ -70,4 +69,18 @@ public class TestCache2 {
         assertEquals("10", str1);
     }
 
+    // Cache + getters
+    @Test
+    public void cacheGetter_3() {
+        Cache<Integer, String> cache = CacheFactory.createPlainCache(2);
+        String str1 = cache.get(1, k -> k.toString());
+        String str2 = cache.get(2, k -> k.toString());
+        String str3 = cache.get(3, k -> k.toString());
+        assertEquals("1", str1);
+        assertEquals("2", str2);
+        assertEquals("3", str3);
+        cache.put(1, "10");
+        str1 = cache.getIfPresent(1);
+        assertEquals("10", str1);
+    }
 }

Reply via email to