All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.geronimo.jcache.simple.SimpleCache Maven / Gradle / Ivy

/*
 * 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.geronimo.jcache.simple;

import static org.apache.geronimo.jcache.simple.Asserts.assertNotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Configuration;
import javax.cache.configuration.Factory;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.EventType;
import javax.cache.expiry.Duration;
import javax.cache.expiry.EternalExpiryPolicy;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriter;
import javax.cache.integration.CacheWriterException;
import javax.cache.integration.CompletionListener;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.cache.processor.EntryProcessorResult;
import javax.management.ObjectName;

public class SimpleCache implements Cache {

    private final SimpleManager manager;

    private final SimpleConfiguration config;

    private final CacheLoader loader;

    private final CacheWriter writer;

    private final ExpiryPolicy expiryPolicy;

    private final ObjectName cacheConfigObjectName;

    private final ObjectName cacheStatsObjectName;

    private final String name;

    private final ConcurrentHashMap, SimpleElement> delegate;

    private final Map, SimpleListener> listeners = new ConcurrentHashMap<>();

    private final Statistics statistics = new Statistics();

    private final ExecutorService pool;

    private final Serializations serializations;

    private final Collection> poolTasks = new CopyOnWriteArraySet<>();

    private volatile boolean closed = false;

    public SimpleCache(final ClassLoader classLoader, final SimpleManager mgr, final String cacheName,
            final SimpleConfiguration configuration, final Properties properties,
            final ExecutorService executorService) {
        manager = mgr;

        name = cacheName;

        final int capacity = Integer.parseInt(property(properties, cacheName, "capacity", "1000"));
        final float loadFactor = Float.parseFloat(property(properties, cacheName, "loadFactor", "0.75"));
        final int concurrencyLevel = Integer.parseInt(property(properties, cacheName, "concurrencyLevel", "16"));
        delegate = new ConcurrentHashMap<>(capacity, loadFactor, concurrencyLevel);
        config = configuration;
        pool = executorService;

        final long evictionPause = Long.parseLong(
                properties.getProperty(cacheName + ".evictionPause", properties.getProperty("evictionPause", "30000")));
        if (evictionPause > 0) {
            final long maxDeleteByEvictionRun = Long.parseLong(property(properties, cacheName, "maxDeleteByEvictionRun", "100"));
            addPoolTask(new EvictionThread(evictionPause, maxDeleteByEvictionRun));
        }

        serializations = new Serializations(property(properties, cacheName, "serialization.whitelist", null));

        final Factory> cacheLoaderFactory = configuration.getCacheLoaderFactory();
        if (cacheLoaderFactory == null) {
            loader = NoLoader.INSTANCE;
        } else {
            loader = ExceptionWrapperHandler.newProxy(classLoader, cacheLoaderFactory.create(), CacheLoaderException.class,
                    CacheLoader.class);
        }

        final Factory> cacheWriterFactory = configuration.getCacheWriterFactory();
        if (cacheWriterFactory == null) {
            writer = NoWriter.INSTANCE;
        } else {
            writer = ExceptionWrapperHandler.newProxy(classLoader, cacheWriterFactory.create(), CacheWriterException.class,
                    CacheWriter.class);
        }

        final Factory expiryPolicyFactory = configuration.getExpiryPolicyFactory();
        if (expiryPolicyFactory == null) {
            expiryPolicy = new EternalExpiryPolicy();
        } else {
            expiryPolicy = expiryPolicyFactory.create();
        }

        for (final CacheEntryListenerConfiguration listener : config.getCacheEntryListenerConfigurations()) {
            listeners.put(listener, new SimpleListener<>(listener));
        }

        statistics.setActive(config.isStatisticsEnabled());

        final String mgrStr = manager.getURI().toString().replaceAll(",|:|=|\n", ".");
        final String cacheStr = name.replaceAll(",|:|=|\n", ".");
        try {
            cacheConfigObjectName = new ObjectName(
                    "javax.cache:type=CacheConfiguration," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
            cacheStatsObjectName = new ObjectName(
                    "javax.cache:type=CacheStatistics," + "CacheManager=" + mgrStr + "," + "Cache=" + cacheStr);
        } catch (final Exception e) {
            throw new IllegalArgumentException(e);
        }
        if (config.isManagementEnabled()) {
            JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean(this));
        }
        if (config.isStatisticsEnabled()) {
            JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics));
        }
    }

    private void assertNotClosed() {
        if (isClosed()) {
            throw new IllegalStateException("cache closed");
        }
    }

    @Override
    public V get(final K key) {
        assertNotClosed();
        assertNotNull(key, "key");
        final long getStart = Times.now(false);
        return doGetControllingExpiry(getStart, key, true, false, false, true, loader);
    }

    private V doLoad(final K key, final boolean update, final boolean propagateLoadException, final CacheLoader loader) {
        V v = null;
        try {
            v = loader.load(key);
        } catch (final CacheLoaderException e) {
            if (propagateLoadException) {
                throw e;
            }
        }
        if (v != null) {
            final Duration duration = update ? expiryPolicy.getExpiryForUpdate() : expiryPolicy.getExpiryForCreation();
            if (isNotZero(duration)) {
                delegate.put(new SimpleKey<>(key), new SimpleElement<>(v, duration));
            }
        }
        return v;
    }

    private void touch(final SimpleKey key, final SimpleElement element) {
        if (config.isStoreByValue()) {
            delegate.put(new SimpleKey<>(serializations.copy(manager.getClassLoader(), key.getKey())), element);
        }
    }

    @Override
    public Map getAll(final Set keys) {
        assertNotClosed();
        for (final K k : keys) {
            assertNotNull(k, "key");
        }

        final Map result = new HashMap<>();
        for (final K key : keys) {
            assertNotNull(key, "key");

            final SimpleKey simpleKey = new SimpleKey<>(key);
            final SimpleElement elt = delegate.get(simpleKey);
            V val = elt != null ? elt.getElement() : null;
            if (val == null && config.isReadThrough()) {
                val = doLoad(key, false, false, loader);
                if (val != null) {
                    result.put(key, val);
                }
            } else if (elt != null) {
                final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
                if (isNotZero(expiryForAccess)) {
                    touch(simpleKey, elt);
                    result.put(key, val);
                } else {
                    expires(simpleKey);
                }
            }
        }
        return result;
    }

    @Override
    public boolean containsKey(final K key) {
        assertNotClosed();
        assertNotNull(key, "key");
        return delegate.get(new SimpleKey<>(key)) != null;
    }

    @Override
    public void put(final K key, final V rawValue) {
        assertNotClosed();
        assertNotNull(key, "key");
        assertNotNull(rawValue, "value");

        final boolean storeByValue = config.isStoreByValue();
        final SimpleKey simpleKey = new SimpleKey<>(storeByValue ? serializations.copy(manager.getClassLoader(), key) : key);
        final SimpleElement oldElt = delegate.get(simpleKey);
        final V old = oldElt != null ? oldElt.getElement() : null;
        final V value = storeByValue ? serializations.copy(manager.getClassLoader(), rawValue) : rawValue;

        final boolean created = old == null;
        final Duration duration = created ? expiryPolicy.getExpiryForCreation() : expiryPolicy.getExpiryForUpdate();
        if (isNotZero(duration)) {
            final boolean statisticsEnabled = config.isStatisticsEnabled();
            final long start = Times.now(false);

            writer.write(new SimpleEntry<>(key, value));
            delegate.put(simpleKey, new SimpleElement<>(value, duration));
            if (!listeners.isEmpty()) {
                for (final SimpleListener listener : listeners.values()) {
                    if (created) {
                        listener.onCreated(Collections.> singletonList(
                                new SimpleEvent<>(this, EventType.CREATED, null, key, value)));
                    } else
                        listener.onUpdated(Collections.> singletonList(
                                new SimpleEvent<>(this, EventType.UPDATED, old, key, value)));
                }
            }

            if (statisticsEnabled) {
                statistics.increasePuts(1);
                statistics.addPutTime(Times.now(false) - start);
            }
        } else {
            if (!created) {
                expires(simpleKey);
            }
        }
    }

    private void expires(final SimpleKey cacheKey) {
        final SimpleElement elt = delegate.get(cacheKey);
        delegate.remove(cacheKey);
        onExpired(cacheKey, elt);
    }

    private void onExpired(final SimpleKey cacheKey, final SimpleElement elt) {
        for (final SimpleListener listener : listeners.values()) {
            listener.onExpired(Collections.> singletonList(
                    new SimpleEvent<>(this, EventType.REMOVED, null, cacheKey.getKey(), elt.getElement())));
        }
    }

    @Override
    public V getAndPut(final K key, final V value) {
        assertNotClosed();
        assertNotNull(key, "key");
        assertNotNull(value, "value");
        final long getStart = Times.now(false);
        final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader);
        put(key, value);
        return v;
    }

    @Override
    public void putAll(final Map map) {
        assertNotClosed();
        final TempStateCacheView view = new TempStateCacheView(this);
        for (final Map.Entry e : map.entrySet()) {
            view.put(e.getKey(), e.getValue());
        }
        view.merge();
    }

    @Override
    public boolean putIfAbsent(final K key, final V value) {
        final boolean statisticsEnabled = config.isStatisticsEnabled();
        if (!containsKey(key)) {
            if (statisticsEnabled) {
                statistics.increaseMisses(1);
            }
            put(key, value);
            return true;
        } else {
            if (statisticsEnabled) {
                statistics.increaseHits(1);
            }
        }
        return false;
    }

    @Override
    public boolean remove(final K key) {
        assertNotClosed();
        assertNotNull(key, "key");

        final boolean statisticsEnabled = config.isStatisticsEnabled();
        final long start = Times.now(!statisticsEnabled);

        writer.delete(key);
        final SimpleKey cacheKey = new SimpleKey<>(key);

        final SimpleElement v = delegate.remove(cacheKey);
        if (v == null || v.isExpired()) {
            return false;
        }

        final V value = v.getElement();
        for (final SimpleListener listener : listeners.values()) {
            listener.onRemoved(Collections.> singletonList(
                    new SimpleEvent<>(this, EventType.REMOVED, value, key, value)));
        }
        if (statisticsEnabled) {
            statistics.increaseRemovals(1);
            statistics.addRemoveTime(Times.now(false) - start);
        }

        return true;
    }

    @Override
    public boolean remove(final K key, final V oldValue) {
        assertNotClosed();
        assertNotNull(key, "key");
        assertNotNull(oldValue, "oldValue");
        final long getStart = Times.now(false);
        final V v = doGetControllingExpiry(getStart, key, false, false, false, false, loader);
        if (oldValue.equals(v)) {
            remove(key);
            return true;
        } else if (v != null) {
            // weird but just for stats to be right
            // (org.jsr107.tck.expiry.CacheExpiryTest.removeSpecifiedEntryShouldNotCallExpiryPolicyMethods())
            expiryPolicy.getExpiryForAccess();
        }
        return false;
    }

    @Override
    public V getAndRemove(final K key) {
        assertNotClosed();
        assertNotNull(key, "key");
        final long getStart = Times.now(false);
        final V v = doGetControllingExpiry(getStart, key, false, false, true, false, loader);
        remove(key);
        return v;
    }

    private V doGetControllingExpiry(final long getStart, final K key, final boolean updateAcess, final boolean forceDoLoad,
            final boolean skipLoad, final boolean propagateLoadException, final CacheLoader loader) {
        final boolean statisticsEnabled = config.isStatisticsEnabled();
        final SimpleKey simpleKey = new SimpleKey<>(key);
        final SimpleElement elt = delegate.get(simpleKey);
        V v = elt != null ? elt.getElement() : null;
        if (v == null && (config.isReadThrough() || forceDoLoad)) {
            if (!skipLoad) {
                v = doLoad(key, false, propagateLoadException, loader);
            }
        } else if (statisticsEnabled) {
            if (v != null) {
                statistics.increaseHits(1);
            } else {
                statistics.increaseMisses(1);
            }
        }

        if (updateAcess && elt != null) {
            final Duration expiryForAccess = expiryPolicy.getExpiryForAccess();
            if (!isNotZero(expiryForAccess)) {
                expires(simpleKey);
            }
        }
        if (statisticsEnabled && v != null) {
            statistics.addGetTime(Times.now(false) - getStart);
        }
        return v;
    }

    @Override
    public boolean replace(final K key, final V oldValue, final V newValue) {
        assertNotClosed();
        assertNotNull(key, "key");
        assertNotNull(oldValue, "oldValue");
        assertNotNull(newValue, "newValue");
        final V value = doGetControllingExpiry(Times.now(config.isStatisticsEnabled()), key, false, config.isReadThrough(), false,
                true, loader);
        if (value != null && value.equals(oldValue)) {
            put(key, newValue);
            return true;
        } else if (value != null) {
            expiryPolicy.getExpiryForAccess();
        }
        return false;
    }

    @Override
    public boolean replace(final K key, final V value) {
        assertNotClosed();
        assertNotNull(key, "key");
        assertNotNull(value, "value");
        boolean statisticsEnabled = config.isStatisticsEnabled();
        if (containsKey(key)) {
            if (statisticsEnabled) {
                statistics.increaseHits(1);
            }
            put(key, value);
            return true;
        } else if (statisticsEnabled) {
            statistics.increaseMisses(1);
        }
        return false;
    }

    @Override
    public V getAndReplace(final K key, final V value) {
        assertNotClosed();
        assertNotNull(key, "key");
        assertNotNull(value, "value");

        final boolean statisticsEnabled = config.isStatisticsEnabled();

        final SimpleElement elt = delegate.get(new SimpleKey<>(key));
        if (elt != null) {
            V oldValue = elt.getElement();
            if (oldValue == null && config.isReadThrough()) {
                oldValue = doLoad(key, false, false, loader);
            } else if (statisticsEnabled) {
                statistics.increaseHits(1);
            }
            put(key, value);
            return oldValue;
        } else if (statisticsEnabled) {
            statistics.increaseMisses(1);
        }
        return null;
    }

    @Override
    public void removeAll(final Set keys) {
        assertNotClosed();
        assertNotNull(keys, "keys");
        for (final K k : keys) {
            remove(k);
        }
    }

    @Override
    public void removeAll() {
        assertNotClosed();
        for (final SimpleKey k : delegate.keySet()) {
            remove(k.getKey());
        }
    }

    @Override
    public void clear() {
        assertNotClosed();
        delegate.clear();
    }

    @Override
    public > C2 getConfiguration(final Class clazz) {
        assertNotClosed();
        return clazz.cast(config);
    }

    @Override
    public void loadAll(final Set keys, final boolean replaceExistingValues,
            final CompletionListener completionListener) {
        assertNotClosed();
        assertNotNull(keys, "keys");
        if (loader == null) { // quick exit path
            if (completionListener != null) {
                completionListener.onCompletion();
            }
            return;
        }
        for (final K k : keys) {
            assertNotNull(k, "a key");
        }
        addPoolTask(new Runnable() {

            @Override
            public void run() {
                doLoadAll(keys, replaceExistingValues, completionListener);
            }
        });
    }

    private void addPoolTask(final Runnable runnable) {
        final AtomicReference> ref = new AtomicReference<>();
        final CountDownLatch refIsSet = new CountDownLatch(1);
        ref.set(pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } finally {
                    try {
                        refIsSet.await();
                    } catch (final InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    poolTasks.remove(ref.get());
                }
            }
        }));
        refIsSet.countDown();
        poolTasks.add(ref.get());
    }

    private void doLoadAll(final Set keys, final boolean replaceExistingValues,
            final CompletionListener completionListener) {
        try {
            final long now = Times.now(false);
            final Map kvMap = loader.loadAll(keys);
            if (kvMap == null) {
                return;
            }
            final CacheLoader preloaded = new MapLoader<>(kvMap);
            for (final K k : keys) {
                if (replaceExistingValues) {
                    doLoad(k, containsKey(k), completionListener != null, preloaded);
                } else if (!containsKey(k)) {
                    doGetControllingExpiry(now, k, true, true, false, completionListener != null, preloaded);
                }
            }
        } catch (final RuntimeException e) {
            if (completionListener != null) {
                completionListener.onException(e);
                return;
            }
        }
        if (completionListener != null) {
            completionListener.onCompletion();
        }
    }

    @Override
    public  T invoke(final K key, final EntryProcessor entryProcessor, final Object... arguments)
            throws EntryProcessorException {
        final TempStateCacheView view = new TempStateCacheView(this);
        final T t = doInvoke(view, key, entryProcessor, arguments);
        view.merge();
        return t;
    }

    private  T doInvoke(final TempStateCacheView view, final K key, final EntryProcessor entryProcessor,
            final Object... arguments) {
        assertNotClosed();
        assertNotNull(entryProcessor, "entryProcessor");
        assertNotNull(key, "key");
        try {
            if (config.isStatisticsEnabled()) {
                if (containsKey(key)) {
                    statistics.increaseHits(1);
                } else {
                    statistics.increaseMisses(1);
                }
            }
            return entryProcessor.process(new SimpleMutableEntry<>(view, key), arguments);
        } catch (final Exception ex) {
            return throwEntryProcessorException(ex);
        }
    }

    @Override
    public  Map> invokeAll(final Set keys,
            final EntryProcessor entryProcessor, final Object... arguments) {
        assertNotClosed();
        assertNotNull(entryProcessor, "entryProcessor");
        final Map> results = new HashMap<>();
        for (final K k : keys) {
            try {
                final T invoke = invoke(k, entryProcessor, arguments);
                if (invoke != null) {
                    results.put(k, new EntryProcessorResult() {

                        @Override
                        public T get() throws EntryProcessorException {
                            return invoke;
                        }
                    });
                }
            } catch (final Exception e) {
                results.put(k, new EntryProcessorResult() {

                    @Override
                    public T get() throws EntryProcessorException {
                        return throwEntryProcessorException(e);
                    }
                });
            }
        }
        return results;
    }

    @Override
    public void registerCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) {
        assertNotClosed();
        if (listeners.containsKey(cacheEntryListenerConfiguration)) {
            throw new IllegalArgumentException(cacheEntryListenerConfiguration + " already registered");
        }
        listeners.put(cacheEntryListenerConfiguration, new SimpleListener<>(cacheEntryListenerConfiguration));
        config.addListener(cacheEntryListenerConfiguration);
    }

    @Override
    public void deregisterCacheEntryListener(final CacheEntryListenerConfiguration cacheEntryListenerConfiguration) {
        assertNotClosed();
        listeners.remove(cacheEntryListenerConfiguration);
        config.removeListener(cacheEntryListenerConfiguration);
    }

    @Override
    public Iterator> iterator() {
        assertNotClosed();
        final Iterator> keys = new HashSet<>(delegate.keySet()).iterator();
        return new Iterator>() {

            private K lastKey = null;

            @Override
            public boolean hasNext() {
                return keys.hasNext();
            }

            @Override
            public Entry next() {
                lastKey = keys.next().getKey();
                return new SimpleEntry<>(lastKey, get(lastKey));
            }

            @Override
            public void remove() {
                if (isClosed() || lastKey == null) {
                    throw new IllegalStateException(isClosed() ? "cache closed" : "call next() before remove()");
                }
                SimpleCache.this.remove(lastKey);
            }
        };
    }

    @Override
    public String getName() {
        assertNotClosed();
        return name;
    }

    @Override
    public CacheManager getCacheManager() {
        assertNotClosed();
        return manager;
    }

    @Override
    public synchronized void close() {
        if (isClosed()) {
            return;
        }

        for (final Future task : poolTasks) {
            task.cancel(true);
        }

        final CacheException ce = new CacheException();
        manager.release(getName());
        closed = true;
        close(loader, ce);
        close(writer, ce);
        close(expiryPolicy, ce);
        for (final SimpleListener listener : listeners.values()) {
            try {
                listener.close();
            } catch (final Exception e) {
                ce.addSuppressed(e);
            }
        }
        listeners.clear();
        JMXs.unregister(cacheConfigObjectName);
        JMXs.unregister(cacheStatsObjectName);
        delegate.clear();
        if (ce.getSuppressed().length > 0) {
            throw ce;
        }
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    @Override
    public  T unwrap(final Class clazz) {
        assertNotClosed();
        if (clazz.isInstance(this)) {
            return clazz.cast(this);
        }
        if (clazz.isAssignableFrom(Map.class) || clazz.isAssignableFrom(ConcurrentMap.class)) {
            return clazz.cast(delegate);
        }
        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
    }

    public Statistics getStatistics() {
        return statistics;
    }

    public void enableManagement() {
        config.managementEnabled();
        JMXs.register(cacheConfigObjectName, new SimpleCacheMXBean(this));
    }

    public void disableManagement() {
        config.managementDisabled();
        JMXs.unregister(cacheConfigObjectName);
    }

    public void enableStatistics() {
        config.statisticsEnabled();
        statistics.setActive(true);
        JMXs.register(cacheStatsObjectName, new SimpleCacheStatisticsMXBean(statistics));
    }

    public void disableStatistics() {
        config.statisticsDisabled();
        statistics.setActive(false);
        JMXs.unregister(cacheStatsObjectName);
    }

    private static String property(final Properties properties, final String cacheName, final String name,
            final String defaultValue) {
        return properties.getProperty(cacheName + "." + name, properties.getProperty(name, defaultValue));
    }

    private static boolean isNotZero(final Duration duration) {
        return duration == null || !duration.isZero();
    }

    private static  T throwEntryProcessorException(final Exception ex) {
        if (EntryProcessorException.class.isInstance(ex)) {
            throw EntryProcessorException.class.cast(ex);
        }
        throw new EntryProcessorException(ex);
    }

    private static void close(final Object potentiallyCloseable, final CacheException wrapper) {
        if (AutoCloseable.class.isInstance(potentiallyCloseable)) {
            try {
                AutoCloseable.class.cast(potentiallyCloseable).close();
            } catch (final Exception re) {
                wrapper.addSuppressed(re);
            }
        }
    }

    private class EvictionThread implements Runnable {

        private final long pause;

        private final long maxDelete;

        private EvictionThread(final long evictionPause, final long maxDelete) {
            this.pause = evictionPause;
            this.maxDelete = maxDelete;
        }

        @Override
        public void run() {
            while (!isClosed()) {
                try {
                    Thread.sleep(pause);
                } catch (final InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
                if (delegate.isEmpty()) {
                    continue;
                }

                try {
                    final List> keys = new ArrayList<>(delegate.keySet());
                    Collections.sort(keys, new Comparator>() {

                        @Override
                        public int compare(final SimpleKey o1, final SimpleKey o2) {
                            final long l = o1.lastAccess() - o2.lastAccess();
                            if (l == 0) {
                                return keys.indexOf(o1) - keys.indexOf(o2);
                            }
                            return (int) l;
                        }
                    });

                    int delete = 0;
                    for (final SimpleKey key : keys) {
                        final SimpleElement elt = delegate.get(key);
                        if (elt != null && elt.isExpired()) {
                            delegate.remove(key);
                            statistics.increaseEvictions(1);
                            onExpired(key, elt);
                            delete++;
                            if (delete >= maxDelete) {
                                break;
                            }
                        }
                    }
                } catch (final Exception e) {
                    // no-op
                }
            }
        }
    }

    private static class MapLoader implements CacheLoader {

        private final Map loaded;

        private MapLoader(final Map loaded) {
            this.loaded = loaded;
        }

        @Override
        public V load(final K key) throws CacheLoaderException {
            return loaded.get(key);
        }

        @Override
        public Map loadAll(final Iterable keys) throws CacheLoaderException {
            throw new UnsupportedOperationException();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy