/******************************************************************************* * Copyright (c) 2007, 2014 compeople 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.core.cache; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.LinkedList; import org.osgi.service.log.LogService; import org.eclipse.equinox.log.Logger; import org.eclipse.riena.core.Log4r; import org.eclipse.riena.internal.core.Activator; import org.eclipse.riena.internal.core.cache.ICacheEntry; import org.eclipse.riena.internal.core.cache.SoftCacheEntry; /** * Class implements a generic object cache. For a detailed description @see * IGenericObjectCache * * Beyond the interface description, this implementation uses SoftReferences to * keep objects beyond the minSize as long the JVM memory allows it (no * GarbageCollection) and no timeout for an entry occurred. * */ public class GenericObjectCache<K, V> implements IGenericObjectCache<K, V> { private final HashMap<K, ICacheEntry<K, V>> cacheEntries; /** timeout in milliseconds * */ private long timeout; /** minimum count of entries to keep * */ private int minimumSize; private final LinkedList<V> hardLinks; /** Reference queue for cleared SoftReference objects. */ private final ReferenceQueue<V> queue; private int statHit; private int statNotFound; private int statMiss; private int statTimeout; private static int statDisplayCount; private String name = "Cache : "; //$NON-NLS-1$ private final static Logger LOGGER = Log4r.getLogger(Activator.getDefault(), GenericObjectCache.class); /** * Constructor. */ public GenericObjectCache() { super(); LOGGER.log(LogService.LOG_DEBUG, "creating new GenericObjectCache instance"); //$NON-NLS-1$ cacheEntries = new HashMap<K, ICacheEntry<K, V>>(); queue = new ReferenceQueue<V>(); hardLinks = new LinkedList<V>(); // default timeout 1 minute setTimeout(60000); } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#setName(java.lang.String) */ public void setName(final String name) { this.name = name + " : "; //$NON-NLS-1$ } /* * (non-Javadoc) * * @see * org.eclipse.riena.core.cache.IGenericObjectCache#get(java.lang.Object) */ public V get(final K key) { LOGGER.log(LogService.LOG_DEBUG, "get = " + key); //$NON-NLS-1$ long timestamp = 0; V value = null; synchronized (cacheEntries) { final ICacheEntry<K, V> entry = cacheEntries.get(key); /** do we find the entry * */ if (entry == null) { statNotFound++; printStat(); return null; } timestamp = entry.getTimestamp(); final long timePassed = System.currentTimeMillis() - timestamp; /** is the entry expired * */ if (timePassed > timeout) { remove(key); statTimeout++; printStat(); return null; } value = entry.getValue(); } /** does the soft reference still point anywhere * */ if (value != null) { /** if a minimum size is required * */ touchValue(value); statHit++; printStat(); return value; } else { /** if not remove the key * */ remove(key); statMiss++; printStat(); return null; } } private void printStat() { statDisplayCount++; if (statDisplayCount > 100) { LOGGER.log(LogService.LOG_INFO, name + "Hit / NotFound / Miss / Timeout " + statHit + " / " + statNotFound //$NON-NLS-1$ //$NON-NLS-2$ + " / " + statMiss + " / " + statTimeout); //$NON-NLS-1$ //$NON-NLS-2$ statDisplayCount = 0; } } public String getStatistic() { return name + "Hit / NotFound / Miss / Timeout " //$NON-NLS-1$ + statHit + " / " + statNotFound + " / " + statMiss + " / " + statTimeout;//$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ } private void touchValue(final V value) { if (minimumSize > 0) { synchronized (hardLinks) { hardLinks.addFirst(value); if (hardLinks.size() > minimumSize) { // Remove the last entry if list longer than minimumSize hardLinks.removeLast(); } } } } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#put(Object, * java.lang.Object) */ public void put(final K key, final V value) { LOGGER.log(LogService.LOG_DEBUG, "put = " + key + " , " + value + ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ processQueue(); ICacheEntry<K, V> entry; entry = new SoftCacheEntry<K, V>(value, key, queue); synchronized (cacheEntries) { cacheEntries.put(key, entry); } touchValue(value); } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#clear() */ public void clear() { LOGGER.log(LogService.LOG_DEBUG, "clear"); //$NON-NLS-1$ synchronized (hardLinks) { hardLinks.clear(); } processQueue(); synchronized (cacheEntries) { cacheEntries.clear(); } } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#remove(Object) */ public void remove(final K key) { LOGGER.log(LogService.LOG_DEBUG, "remove = " + key); //$NON-NLS-1$ processQueue(); synchronized (cacheEntries) { cacheEntries.remove(key); } } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#size() */ public int size() { processQueue(); synchronized (cacheEntries) { LOGGER.log(LogService.LOG_DEBUG, "size returned = " + cacheEntries.size()); //$NON-NLS-1$ return cacheEntries.size(); } } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#setTimeout(int) */ public void setTimeout(final int milliseconds) { LOGGER.log(LogService.LOG_DEBUG, "setTimeout = " + milliseconds); //$NON-NLS-1$ timeout = milliseconds; } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#getTimeout() */ public int getTimeout() { return (int) timeout; } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#getMinimumSize() */ public int getMinimumSize() { return minimumSize; } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#getSize() */ public int getSize() { return size(); } /** * @see org.eclipse.riena.core.cache.IGenericObjectCache#setMinimumSize(int) */ public void setMinimumSize(final int minSize) { LOGGER.log(LogService.LOG_DEBUG, "setMinSize = " + minSize); //$NON-NLS-1$ minimumSize = minSize; } private void processQueue() { LOGGER.log(LogService.LOG_DEBUG, "processQueue"); //$NON-NLS-1$ SoftReference<ICacheEntry<K, V>> ref; int count = 0; synchronized (cacheEntries) { while ((ref = (SoftReference<ICacheEntry<K, V>>) queue.poll()) != null) { final ICacheEntry<K, V> entry = ref.get(); if (entry != null) { cacheEntries.remove(entry.getKey()); count++; } } } if (count > 0) { LOGGER.log(LogService.LOG_INFO, "processQueue removed " + count + " entries"); //$NON-NLS-1$ //$NON-NLS-2$ } } }