package org.netbeans.gradle.project; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.jtrim.cancel.Cancellation; import org.jtrim.cancel.CancellationToken; import org.jtrim.concurrent.WaitableSignal; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.project.model.ModelLoader; import org.netbeans.gradle.project.model.ModelRetrievedListener; import org.netbeans.gradle.project.tasks.GradleDaemonManager; public final class ProjectModelUpdater<M> { private final ModelLoader<? extends M> modelLoader; private final ModelRetrievedListener<M> modelUpdaterWrapper; private final AtomicBoolean hasModelBeenLoaded; private final WaitableSignal loadedAtLeastOnceSignal; private final AtomicReference<Object> lastInProgressRef; public ProjectModelUpdater( ModelLoader<? extends M> modelLoader, final ModelRetrievedListener<? super M> modelUpdater) { ExceptionHelper.checkNotNullArgument(modelLoader, "modelLoader"); ExceptionHelper.checkNotNullArgument(modelUpdater, "modelUpdater"); this.modelLoader = modelLoader; this.modelUpdaterWrapper = new ModelRetrievedListener<M>() { @Override public void updateModel(M model, Throwable error) { try { modelUpdater.updateModel(model, error); } finally { loadedAtLeastOnceSignal.signal(); } } }; this.hasModelBeenLoaded = new AtomicBoolean(false); this.loadedAtLeastOnceSignal = new WaitableSignal(); this.lastInProgressRef = new AtomicReference<>(null); } public boolean wasModelEverSet() { return loadedAtLeastOnceSignal.isSignaled(); } public void ensureLoadRequested() { loadProject(true, true); } public void reloadProject() { loadProject(false, false); } public void reloadProjectMayUseCache() { loadProject(false, true); } private void loadProject(final boolean onlyIfNotLoaded, final boolean mayUseCache) { if (!hasModelBeenLoaded.compareAndSet(false, true)) { if (onlyIfNotLoaded) { return; } } final Object progressRef = new Object(); if (mayUseCache) { Object currentProgressRef; do { currentProgressRef = lastInProgressRef.get(); if (currentProgressRef != null) { // Since we are content with a cached value, we consider that the // model loading currently in progress will be (or was) good enough // as a cached value. return; } } while (!lastInProgressRef.compareAndSet(null, progressRef)); } else { lastInProgressRef.set(progressRef); } modelLoader.fetchModel(mayUseCache, modelUpdaterWrapper, new Runnable() { @Override public void run() { lastInProgressRef.compareAndSet(progressRef, null); } }); } private static void checkCanWaitForProjectLoad() { if (GradleDaemonManager.isRunningExclusiveTask()) { throw new IllegalStateException("Cannot wait for loading a project" + " while blocking daemon tasks from being executed." + " Possible dead-lock."); } } public boolean tryWaitForLoadedProject(long timeout, TimeUnit unit) { return tryWaitForLoadedProject(Cancellation.UNCANCELABLE_TOKEN, timeout, unit); } public boolean tryWaitForLoadedProject(CancellationToken cancelToken, long timeout, TimeUnit unit) { checkCanWaitForProjectLoad(); ensureLoadRequested(); return loadedAtLeastOnceSignal.tryWaitSignal(cancelToken, timeout, unit); } public void waitForLoadedProject(CancellationToken cancelToken) { checkCanWaitForProjectLoad(); ensureLoadRequested(); loadedAtLeastOnceSignal.waitSignal(cancelToken); } }