package org.netbeans.gradle.project.model; import java.io.File; import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import org.jtrim.event.CopyOnTriggerListenerManager; import org.jtrim.event.EventDispatcher; import org.jtrim.event.ListenerManager; import org.jtrim.event.ListenerRef; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.model.util.CollectionUtils; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; public final class GradleModelCache { private final ReentrantLock cacheLock; private final Map<CacheKey, NbGradleModel> cache; private final AtomicInteger maxCapacity; private final ListenerManager<ProjectModelUpdatedListener> updateListeners; public GradleModelCache(int maxCapacity) { if (maxCapacity < 0) { throw new IllegalArgumentException("Illegal max. capacity value: " + maxCapacity); } this.cacheLock = new ReentrantLock(); this.maxCapacity = new AtomicInteger(maxCapacity); this.cache = CollectionUtils.newLinkedHashMap(maxCapacity); this.updateListeners = new CopyOnTriggerListenerManager<>(); } private void cleanupCacheUnsafe() { assert cacheLock.isHeldByCurrentThread(); int currentMaxCapacity = maxCapacity.get(); // Don't create the iterator. if (cache.size() <= currentMaxCapacity) { return; } Iterator<?> itr = cache.entrySet().iterator(); while (cache.size() > currentMaxCapacity && itr.hasNext()) { itr.next(); itr.remove(); } } private void cleanupCache() { cacheLock.lock(); try { cleanupCacheUnsafe(); } finally { cacheLock.unlock(); } } public int getMaxCapacity() { return maxCapacity.get(); } public void setMaxCapacity(int newMaxCapacity) { if (newMaxCapacity < 0) { throw new IllegalArgumentException("Illegal max. capacity value: " + newMaxCapacity); } int prevCapacity = maxCapacity.getAndSet(newMaxCapacity); if (prevCapacity > newMaxCapacity) { cleanupCache(); } } public void setMaxCapacityToAtLeast(int newMaxCapacity) { if (newMaxCapacity < 0) { throw new IllegalArgumentException("Illegal max. capacity value: " + newMaxCapacity); } int prevCapacity = maxCapacity.get(); do { if (prevCapacity >= newMaxCapacity) { break; } } while (maxCapacity.compareAndSet(prevCapacity, newMaxCapacity)); } private static CacheKey tryCreateKey(NbGradleModel model) { ExceptionHelper.checkNotNullArgument(model, "model"); File projectDir = model.getGenericInfo().getProjectDir(); FileObject settingsFileObj = model.getGenericInfo().tryGetSettingsFileObj(); File settingsFile = settingsFileObj != null ? FileUtil.toFile(settingsFileObj) : null; if (projectDir == null || (settingsFile == null && settingsFileObj != null)) { return null; } return new CacheKey(projectDir, settingsFile); } public ListenerRef addModelUpdateListener(ProjectModelUpdatedListener listener) { return updateListeners.registerListener(listener); } private void notifyUpdate(NbGradleModel newModel) { updateListeners.onEvent(ModelUpdateDispatcher.INSTANCE, newModel); } public NbGradleModel updateEntry(NbGradleModel model) { CacheKey key = tryCreateKey(model); if (key == null) { return null; } NbGradleModel newModel = model; NbGradleModel prevModel; cacheLock.lock(); try { prevModel = cache.get(key); if (prevModel == null) { cache.put(key, newModel); cleanupCacheUnsafe(); } else { newModel = prevModel.updateEntry(newModel); cache.put(key, newModel); } } finally { cacheLock.unlock(); } if (prevModel != null) { notifyUpdate(model); } return newModel; } public void replaceEntry(NbGradleModel model) { CacheKey key = tryCreateKey(model); if (key == null) { return; } NbGradleModel prevModel; cacheLock.lock(); try { prevModel = cache.put(key, model); cleanupCacheUnsafe(); } finally { cacheLock.unlock(); } if (prevModel != null && prevModel != model) { notifyUpdate(model); } } public NbGradleModel tryGet(File projectDir, File settingsFile) { CacheKey key = new CacheKey(projectDir, settingsFile); cacheLock.lock(); try { return cache.get(key); } finally { cacheLock.unlock(); } } private static class CacheKey { private final File projectDir; private final File settingsFile; public CacheKey(File projectDir, File settingsFile) { ExceptionHelper.checkNotNullArgument(projectDir, "projectDir"); this.projectDir = projectDir; this.settingsFile = settingsFile; } @Override public int hashCode() { int hash = 7; hash = 89 * hash + projectDir.hashCode(); hash = 89 * hash + Objects.hashCode(settingsFile); return hash; } @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final CacheKey other = (CacheKey)obj; return Objects.equals(this.settingsFile, other.settingsFile) && Objects.equals(this.projectDir, other.projectDir); } } private enum ModelUpdateDispatcher implements EventDispatcher<ProjectModelUpdatedListener, NbGradleModel> { INSTANCE; @Override public void onEvent(ProjectModelUpdatedListener eventListener, NbGradleModel arg) { eventListener.onUpdateProject(arg); } } }