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); } }