package com.oracle.avatar.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import jdk.nashorn.internal.runtime.Source;
/**
* A thread-safe, fixed size {@link ClassMap} that holds entries as SoftReferences
* and guards against memory leaks.
* @author Bryan Atsatt. Copyright (c) 2013 Oracle.
* @since 7/29/13
*/
public class ClassCache implements ClassMap {
private static class IndexReference extends SoftReference {
private final Integer index;
public IndexReference(int index, Entry referent, ReferenceQueue q) {
super(referent, q);
this.index = index;
}
public Integer toIndex() {
return index;
}
}
private final Map entries;
private final Map reverseIndex;
private final Map sourceIndex;
private final Map urlIndex;
private final ReferenceQueue queue;
private final int maxSize;
private int minIndex;
private int nextIndex;
/**
* Constructor.
* @param maxSize The maximum cache size.
*/
public ClassCache(int maxSize) {
if (maxSize < 1) throw new Error("minimum max size is 1");
entries = new HashMap<>(maxSize);
reverseIndex = new HashMap<>(maxSize);
sourceIndex = new HashMap<>(maxSize);
urlIndex = new HashMap<>(maxSize);
queue = new ReferenceQueue<>();
this.maxSize = maxSize;
}
/**
* Lookup an entry by {@code URL}.
* @param key The url.
* @return The entry or {@code null} if not found or if the
* entry does not match the URL.
*/
@Override
public synchronized Entry get(URL key) {
removeCollected();
return find(key);
}
/**
* Lookup an entry by {@link Source}.
* @param key The source.
* @return The entry or {@code null} if none.
*/
@Override
public synchronized Entry get(Source key) {
removeCollected();
return find(key);
}
/**
* Add an entry.
* @param source The {@link Source}.
* @param cls The {@link Class};
* @return Any previous {@link Entry}.
*/
@Override
public synchronized Entry put(Source source, Class cls) {
if (source == null) throw new Error("source may not be null");
if (cls == null) throw new Error("class may not be null");
removeCollected();
enforceMaxSize();
final Entry oldEntry = find(source);
final Entry newEntry = new Entry(source, cls);
final IndexReference ref = new IndexReference(nextIndex++, newEntry, queue);
final Integer index = ref.toIndex();
final URL url = source.getURL();
entries.put(index, ref);
if (url != null) {
urlIndex.put(url, index);
}
sourceIndex.put(source, index);
reverseIndex.put(index, source);
return oldEntry;
}
/**
* Return the current size.
* @return The size.
*/
@Override
public synchronized int size() {
removeCollected();
return entries.size();
}
private Entry find(URL key) {
if (key != null) {
final Entry entry = get(urlIndex.get(key));
if (entry != null && entry.equals(key)) {
return entry;
}
}
return null;
}
private Entry find(Source source) {
return get(sourceIndex.get(source));
}
private Entry get(Integer index) {
if (index != null) {
final IndexReference ref = entries.get(index);
if (ref != null) {
return ref.get();
}
}
return null;
}
private void removeCollected() {
for (Object r; (r = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
final IndexReference ref = (IndexReference) r;
remove(ref.toIndex());
}
}
}
private void enforceMaxSize() {
while (entries.size() >= maxSize) {
remove(minIndex++);
}
}
private void remove(final Integer index) {
entries.remove(index);
final Source src = reverseIndex.remove(index);
final URL url = src.getURL();
if (url != null) urlIndex.remove(url);
sourceIndex.remove(src);
}
}