/*
* Licensed 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.jboss.el.util;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
*
* @author jhook
*/
@SuppressWarnings({"hiding", "rawtypes", "unchecked"})
public abstract class ReferenceCache<K,V> {
public abstract class ReferenceFactory<K,V> {
public abstract ReferenceKey<K> createKey(ReferenceQueue queue, K key);
public abstract ReferenceValue<V> createValue(ReferenceQueue queue, V value);
}
private class StrongReferenceFactory extends ReferenceFactory<K,V> {
public ReferenceValue<V> createValue(ReferenceQueue queue, final V value) {
return new ReferenceValue<V>() {
public V get() {
return value;
}
};
}
public ReferenceKey<K> createKey(ReferenceQueue queue, final K key) {
return new ReferenceKey<K>(key) {
public K get() {
return key;
}
};
}
}
private class WeakReferenceFactory extends ReferenceFactory<K,V> {
private class WeakReferenceKey extends ReferenceKey<K> {
private final WeakReference<K> ref;
public WeakReferenceKey(final ReferenceQueue queue, final K key) {
super(key);
this.ref = new WeakReference<K>(key, queue) {
public void clear() {
remove();
super.clear();
}
};
}
public K get() {
return this.ref.get();
}
}
public ReferenceValue<V> createValue(final ReferenceQueue queue, final V value) {
return new ReferenceValue<V>() {
private final WeakReference<V> ref = new WeakReference<V>(value, queue);
public V get() {
return ref.get();
}
};
}
public ReferenceKey<K> createKey(ReferenceQueue queue, K key) {
return new WeakReferenceKey(queue, key);
}
}
private class SoftReferenceFactory extends ReferenceFactory<K,V> {
private class SoftReferenceKey extends ReferenceKey<K> {
private final SoftReference<K> ref;
public SoftReferenceKey(final ReferenceQueue queue, final K key) {
super(key);
this.ref = new SoftReference<K>(key, queue) {
public void clear() {
remove();
super.clear();
}
};
}
public K get() {
return this.ref.get();
}
}
public ReferenceValue<V> createValue(final ReferenceQueue queue, final V value) {
return new ReferenceValue<V>() {
private final SoftReference<V> ref = new SoftReference<V>(value, queue);
public V get() {
return ref.get();
}
};
}
public ReferenceKey<K> createKey(final ReferenceQueue queue, final K key) {
return new SoftReferenceKey(queue, key);
}
}
public abstract class ReferenceKey<K> {
private final int hashCode;
public ReferenceKey(K key) {
this.hashCode = key.hashCode();
}
protected abstract K get();
public boolean equals(Object obj) {
if (this == obj) return true;
K me = this.get();
if (me != null) {
if (obj == me) return true;
if (obj instanceof ReferenceKey) {
K them = ((ReferenceKey<K>) obj).get();
return me == them || me.equals(them);
}
}
return false;
}
public void remove() {
cache.remove(this);
}
public int hashCode() {
return this.hashCode;
}
}
public interface ReferenceValue<V> {
public V get();
}
private class ReferenceQueueRunner
extends ReferenceQueue
implements Runnable
{
public void run() {
while (true) {
try {
Reference ref = this.remove();
if (ref != null) {
ref.clear();
}
} catch (InterruptedException e) {
break;
//e.printStackTrace();
}
}
}
}
private final ConcurrentMap<ReferenceKey<K>,ReferenceValue<V>> cache;
private final ReferenceFactory keyFactory;
private final ReferenceFactory valueFactory;
private final ReferenceFactory lookupFactory;
private final ReferenceQueueRunner queue;
private Thread queueMonitor;
public static enum Type { Strong, Weak, Soft };
/**
* Creates a new instance of ReferenceMap
*/
public ReferenceCache(Type keyType, Type valueType) {
this(keyType, valueType, 0);
}
public ReferenceCache(Type keyType, Type valueType, int initialSize) {
this.keyFactory = toFactory(keyType);
this.valueFactory = toFactory(valueType);
this.lookupFactory = new StrongReferenceFactory();
this.cache = new ConcurrentHashMap<ReferenceKey<K>, ReferenceValue<V>>(initialSize);
this.queue = new ReferenceQueueRunner();
}
public void startMonitor() {
if (queueMonitor == null) {
queueMonitor = new Thread(this.queue);
queueMonitor.setName("jboss EL reference queue cleanup thread");
queueMonitor.setDaemon(true);
queueMonitor.start();
}
}
public void stopMonitor() {
if (queueMonitor!=null) {
queueMonitor.interrupt();
queueMonitor = null;
}
}
private final ReferenceFactory<K,V> toFactory(Type type) {
switch (type) {
case Strong : return new StrongReferenceFactory();
case Weak : return new WeakReferenceFactory();
case Soft : return new SoftReferenceFactory();
default : throw new IllegalArgumentException("Invalid ReferenceType: " + type);
}
}
protected abstract V create(K key);
public V get(final Object key) {
try {
ReferenceKey<K> refKey = this.lookupFactory.createKey(this.queue, (K) key);
ReferenceValue<V> refVal = this.cache.get(refKey);
V value = dereferenceValue(refVal);
if (value != null) {
return value;
} else {
V created = create((K) key);
refVal = valueFactory.createValue(queue, created);
refKey = this.keyFactory.createKey(this.queue, (K) key);
refVal = this.cache.putIfAbsent(refKey, refVal);
value = dereferenceValue(refVal);
if (value == null) {
value = this.create((K) key);
this.put((K) key, value);
}
return value;
}
} catch (Exception e) {
if (e instanceof RuntimeException) throw (RuntimeException) e;
throw new IllegalStateException(e);
}
}
private V dereferenceValue(ReferenceValue<V> refValue) {
return refValue == null ? null : refValue.get();
}
public V put(K key, final V value) {
ReferenceKey refKey = this.keyFactory.createKey(this.queue, key);
ReferenceValue<V> refVal = valueFactory.createValue(queue, value);
refVal = this.cache.putIfAbsent(refKey, refVal);
return value;
}
public V remove(Object key) {
ReferenceKey<K> keyRef = this.lookupFactory.createKey(this.queue, key);
return this.dereferenceValue(this.cache.remove(keyRef));
}
public int size() {
return this.cache.size();
}
public void clear() {
this.cache.clear();
}
}