/*
 * Copyright (c) 2015, PostgreSQL Global Development Group
 * See the LICENSE file in the project root for more information.
 */

package org.postgresql.util;

import java.sql.SQLException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

Caches values in simple least-recently-accessed order.
/** * Caches values in simple least-recently-accessed order. */
public class LruCache<Key, Value extends CanEstimateSize> implements Gettable<Key, Value> {
Action that is invoked when the entry is removed from the cache.
Type parameters:
  • <Value> – type of the cache entry
/** * Action that is invoked when the entry is removed from the cache. * * @param <Value> type of the cache entry */
public interface EvictAction<Value> { void evict(Value value) throws SQLException; }
When the entry is not present in cache, this create action is used to create one.
Type parameters:
  • <Value> – type of the cache entry
/** * When the entry is not present in cache, this create action is used to create one. * * @param <Value> type of the cache entry */
public interface CreateAction<Key, Value> { Value create(Key key) throws SQLException; } private final EvictAction<Value> onEvict; private final CreateAction<Key, Value> createAction; private final int maxSizeEntries; private final long maxSizeBytes; private long currentSize; private final Map<Key, Value> cache; private class LimitedMap extends LinkedHashMap<Key, Value> { LimitedMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor, accessOrder); } @Override protected boolean removeEldestEntry(Map.Entry<Key, Value> eldest) { // Avoid creating iterators if size constraints not violated if (size() <= maxSizeEntries && currentSize <= maxSizeBytes) { return false; } Iterator<Map.Entry<Key, Value>> it = entrySet().iterator(); while (it.hasNext()) { if (size() <= maxSizeEntries && currentSize <= maxSizeBytes) { return false; } Map.Entry<Key, Value> entry = it.next(); evictValue(entry.getValue()); long valueSize = entry.getValue().getSize(); if (valueSize > 0) { // just in case currentSize -= valueSize; } it.remove(); } return false; } } private void evictValue(Value value) { try { onEvict.evict(value); } catch (SQLException e) { /* ignore */ } } public LruCache(int maxSizeEntries, long maxSizeBytes, boolean accessOrder) { this(maxSizeEntries, maxSizeBytes, accessOrder, NOOP_CREATE_ACTION, NOOP_EVICT_ACTION); } public LruCache(int maxSizeEntries, long maxSizeBytes, boolean accessOrder, CreateAction<Key, Value> createAction, EvictAction<Value> onEvict) { this.maxSizeEntries = maxSizeEntries; this.maxSizeBytes = maxSizeBytes; this.createAction = createAction; this.onEvict = onEvict; this.cache = new LimitedMap(16, 0.75f, accessOrder); }
Returns an entry from the cache.
Params:
  • key – cache key
Returns:entry from cache or null if cache does not contain given key.
/** * Returns an entry from the cache. * * @param key cache key * @return entry from cache or null if cache does not contain given key. */
public synchronized Value get(Key key) { return cache.get(key); }
Borrows an entry from the cache.
Params:
  • key – cache key
Throws:
Returns:entry from cache or newly created entry if cache does not contain given key.
/** * Borrows an entry from the cache. * * @param key cache key * @return entry from cache or newly created entry if cache does not contain given key. * @throws SQLException if entry creation fails */
public synchronized Value borrow(Key key) throws SQLException { Value value = cache.remove(key); if (value == null) { return createAction.create(key); } currentSize -= value.getSize(); return value; }
Returns given value to the cache.
Params:
  • key – key
  • value – value
/** * Returns given value to the cache. * * @param key key * @param value value */
public synchronized void put(Key key, Value value) { long valueSize = value.getSize(); if (maxSizeBytes == 0 || maxSizeEntries == 0 || valueSize * 2 > maxSizeBytes) { // Just destroy the value if cache is disabled or if entry would consume more than a half of // the cache evictValue(value); return; } currentSize += valueSize; Value prev = cache.put(key, value); if (prev == null) { return; } // This should be a rare case currentSize -= prev.getSize(); if (prev != value) { evictValue(prev); } }
Puts all the values from the given map into the cache.
Params:
  • m – The map containing entries to put into the cache
/** * Puts all the values from the given map into the cache. * * @param m The map containing entries to put into the cache */
public synchronized void putAll(Map<Key, Value> m) { for (Map.Entry<Key, Value> entry : m.entrySet()) { this.put(entry.getKey(), entry.getValue()); } } public static final CreateAction NOOP_CREATE_ACTION = new CreateAction() { @Override public Object create(Object o) throws SQLException { return null; } }; public static final EvictAction NOOP_EVICT_ACTION = new EvictAction() { @Override public void evict(Object o) throws SQLException { return; } }; }