/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.cache; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; /** * Represents a cache for arbitrary data objects. * <p> * This class needs to be extended by a caching strategy. * </p> * * @param <K> type of the key. * @param <V> type of the value. * @param <M> type of the cache meta information. */ public abstract class CacheBase<K, V, M> implements Cache<K, V> { private Map<K, V> cache = new HashMap<K, V>(); private Map<K, M> metaInfos = new HashMap<K, M>(); private int cacheSize; protected abstract M createMetaInfo(K key); protected abstract K calcEntryToDiscard(Map<K, M> metaInfos); protected void beforeAccess(K key) { } protected abstract M onAccess(K key, M metaInfo); protected CacheBase(int cacheSize) { this(cacheSize, null); } protected CacheBase(int cacheSize, Cache<K, V> cache) { if (cacheSize < 1) { throw new IllegalArgumentException("argument 'cacheSize' must be a positive integer"); //$NON-NLS-1$ } this.cacheSize = cacheSize; if (cache != null) { for (Map.Entry<K, V> entry: cache.entrySet()) { put(entry.getKey(), entry.getValue()); } } } @Override public synchronized final void put(K key, V value) { if (key == null) { throw new IllegalArgumentException("argument 'key' must not be null"); //$NON-NLS-1$ } beforeAccess(key); synchronized (cache) { if (cache.size() >= cacheSize) { K keyDiscard = calcEntryToDiscard(metaInfos); if (keyDiscard == null || !cache.containsKey(keyDiscard)) { // Strategy did not determine a valid key to be removed, so just // decide randomly as a fallback strategy List<K> keys = new ArrayList<K>(cache.keySet()); int position = new Random(System.currentTimeMillis()).nextInt(keys.size()); keyDiscard = keys.get(position); } cache.remove(keyDiscard); metaInfos.remove(keyDiscard); } cache.put(key, value); metaInfos.put(key, createMetaInfo(key)); } } @Override public synchronized final V get(K key) { if (key == null) { throw new IllegalArgumentException("argument 'key' must not be null"); //$NON-NLS-1$ } beforeAccess(key); synchronized (cache) { V value = cache.get(key); if (value != null) { M newMetaInfo = onAccess(key, metaInfos.get(key)); metaInfos.put(key, newMetaInfo); } return value; } } @Override public synchronized final void clear() { synchronized (cache) { cache.clear(); metaInfos.clear(); } } @Override public synchronized final Collection<V> values() { beforeAccess(null); synchronized (cache) { return cache.values(); } } @Override public synchronized final Set<Map.Entry<K, V>> entrySet() { beforeAccess(null); synchronized (cache) { return cache.entrySet(); } } }