/***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.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.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2015 Karol Bucek <self@kares.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.ext.openssl.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* a cache of a kind
*
* @author kares
*/
public class Cache<K, T> {
private static Cache NULL;
private Cache() { /* empty-cache */ }
@SuppressWarnings("unchecked")
public static <K, T> Cache<K, T> getNullCache() {
if ( NULL != null ) return NULL;
return NULL = new Cache<K, T>();
}
/**
* a soft-reference cache
* @param <K>
* @param <T>
* @return new cache instance
*/
public static <K, T> Cache<K, T> newSoftCache() {
return new SoftCache<K, T>();
}
/**
* a soft-reference cache which holds strong references up to the specified
* size, these are arranged in LRU order
* @param <K>
* @param <T>
* @param size
* @return new cache instance
*/
public static <K, T> Cache<K, T> newStrongSoftCache(final int size) {
return new SoftCache<K, T>(size);
}
public T get(K key) {
return null;
}
public T put(K key, T value) {
return null;
}
public T remove(K key) {
return null;
}
public void clear() {
return;
}
public int size() {
return 0;
}
static final class SoftCache<K, T> extends Cache<K, T> {
private final Map<K, Ref<K, T>> cache; // SoftHashMap
private final ReferenceQueue<T> refQueue = new ReferenceQueue<T>();
private final int strongLimit;
private final SortedMap<Ref<K, T>, T> strongRefs; // final Deque<T> strong;
private SoftCache() {
this.strongLimit = 0;
this.cache = new ConcurrentHashMap<K, Ref<K, T>>();
this.strongRefs = null;
}
private SoftCache(final int limit) {
this.strongLimit = limit;
final int capacity = Math.min(limit, 32);
this.cache = new ConcurrentHashMap<K, Ref<K, T>>(capacity);
this.strongRefs = new TreeMap<Ref<K, T>, T>();
}
public T get(K key) {
T result = null;
final Ref<K, T> ref = cache.get(key);
if ( ref != null ) {
result = ref.get();
if ( result == null ) cache.remove(key);
else {
if ( strongRefs != null ) {
synchronized (strongRefs) {
strongRefs.remove(ref);
strongRefs.put(ref.recordAccess(), result);
if ( strongLimit > 0 && strongRefs.size() > strongLimit ) {
strongRefs.remove( strongRefs.firstKey() );
}
}
}
}
}
return result;
}
public T put(K key, T value) {
purgeRefQueue();
final SoftReference<T> prev = cache.put(key, new Ref<K, T>(value, key, refQueue));
return prev == null ? null : prev.get();
}
public T remove(K key) {
purgeRefQueue();
final SoftReference<T> removed = cache.remove(key);
return removed == null ? null : removed.get();
}
public void clear() {
if ( strongRefs != null ) {
synchronized (strongRefs) { strongRefs.clear(); }
}
purgeRefQueue();
cache.clear();
purgeRefQueue();
}
public int size() {
purgeRefQueue();
return cache.size();
}
@SuppressWarnings("unchecked")
private void purgeRefQueue() {
Ref<K, T> ref;
while ( ( ref = (Ref) refQueue.poll() ) != null ) {
synchronized (refQueue) { cache.remove( ref.key ); }
}
}
private static class Ref<K, T> extends SoftReference<T> implements Comparable<Ref> {
private final K key;
volatile long access;
private Ref(T value, K key, ReferenceQueue<T> queue) {
super(value, queue);
this.key = key;
recordAccess();
}
final Ref<K, T> recordAccess() { access = System.currentTimeMillis(); return this; }
@Override
public boolean equals(Object obj) {
if ( obj instanceof Ref ) {
return this.key.equals( ((Ref) obj).key );
}
return false;
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override // order by access time - more recent first (less than) others
public int compareTo(final Ref that) {
final long diff = this.access - that.access;
if ( diff == 0 ) return 0;
// diff > 0 ... this.access > that.access ... this > that
return diff > 0 ? +1 : -1; // this accessed after that
}
}
}
}