package org.jbehave.eclipse.cache; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaModelException; import org.jbehave.eclipse.editor.step.StepCandidate; import org.jbehave.eclipse.util.MonitoredExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fj.Effect; /** * Responsible for loading caches and providing the most recent instance */ public class StepCandidateCacheLoader { private static final Logger log = LoggerFactory .getLogger(StepCandidateCacheLoader.class); private final Executor scanningExecutor; private final Executor completionExecutor; private final StepCandidateCacheListener listener; private final AtomicBoolean loadInProgress = new AtomicBoolean(false); private Runnable pendingLoadRequest; /** * Constructor. The completionExecutor must work with a different thread * pool than the scanningExecutor. Otherwise a dead lock will happen. * * @param listener * the listener that will be informed of completion. * @param scanningExecutor * the executor that will perform the scanning. * @param completionExecutor * the executor that will wait for the scan completion and issue * the callback. */ public StepCandidateCacheLoader(final StepCandidateCacheListener listener, final Executor scanningExecutor, final Executor completionExecutor) { this.listener = listener; this.scanningExecutor = scanningExecutor; this.completionExecutor = completionExecutor; } /** * Requests to load and fill the given cache asynchronously. This method can * be called any number of times, as long as a reload is in progress, only * the last request will be processed when the current one finished. * * @param cache * the cache to fill and return when done. * @param project * the project from which to extract data. * @param scanInitializer * the initializer used for the scanner. */ public void requestReload(MethodCache<StepCandidate> cache, IJavaProject project, Effect<JavaScanner<?>> scanInitializer) { Runnable request = getReloadAsRunnable(cache, project, scanInitializer); synchronized (this.loadInProgress) { if (this.loadInProgress.get()) { this.pendingLoadRequest = request; } else { executeLoadRequest(request); } } } private Runnable getReloadAsRunnable( final MethodCache<StepCandidate> cache, final IJavaProject project, final Effect<JavaScanner<?>> scanInitializer) { return new Runnable() { public void run() { log.info("Rebuilding cache for project " + project.getElementName()); final MonitoredExecutor rebuildProcess = new MonitoredExecutor( scanningExecutor); try { cache.rebuild(project, scanInitializer, rebuildProcess); } catch (JavaModelException e) { log.error("Error during startup of cache rebuild", e); } completionExecutor.execute(new Runnable() { public void run() { try { rebuildProcess.awaitCompletion(); onCacheLoaded(cache); } catch (InterruptedException e) { Thread.interrupted(); log.warn("Cache rebuild interrupted"); onCacheLoadComplete(); } } }); } }; } private void onCacheLoaded(MethodCache<StepCandidate> cache) { this.listener.cacheLoaded(cache); onCacheLoadComplete(); } private void executeLoadRequest(Runnable request) { this.loadInProgress.set(true); request.run(); } private void onCacheLoadComplete() { synchronized (this.loadInProgress) { if (pendingLoadRequest != null) { Runnable request = this.pendingLoadRequest; this.pendingLoadRequest = null; executeLoadRequest(request); } else { this.loadInProgress.set(false); } } } }