/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.core.internal.builder; import com.google.dart.engine.context.AnalysisContext; import com.google.dart.tools.core.analysis.model.ContextManager; import com.google.dart.tools.core.analysis.model.Project; import com.google.dart.tools.core.analysis.model.ProjectManager; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import java.util.ArrayList; /** * Instances of {@code AnalysisManager} manage a queue of {@link AnalysisWorker} instances and * perform analysis via those instances. */ public class AnalysisManager { /** * The instance of {@link AnalysisManager} typically used for background processing. */ private static final AnalysisManager INSTANCE = new AnalysisManager(); /** * @return the default background analysis processor. */ public static AnalysisManager getInstance() { return INSTANCE; } /** * A collection of workers waiting to perform analysis. Synchronize against this field before * accessing it. */ private final ArrayList<AnalysisWorker> backgroundQueue = new ArrayList<AnalysisWorker>(); /** * The background job on which the queued workers are executed, or {@code null} if none. * Synchronize against {@link backgroundQueue} before accessing this field. */ private Job backgroundJob = null; /** * The currently executing {@link AnalysisWorker}. */ private AnalysisWorker activeWorker = null; /** * Flag indicating whether {@link #stopBackgroundAnalysis()} has been called. */ private boolean stopped = false; /** * Add the given worker to the queue of workers that will be processed. * * @param worker the worker to add (not {@code null}) */ public void addWorker(AnalysisWorker worker) { synchronized (backgroundQueue) { if (!backgroundQueue.contains(worker)) { backgroundQueue.add(0, worker); startBackgroundAnalysis(); } } } /** * Answer the currently executing {@link AnalysisWorker}. * * @return the worker or {@code null} if none */ public AnalysisWorker getActiveWorker() { synchronized (backgroundQueue) { return activeWorker; } } /** * Answer the next queued worker or {@code null} if the processor is paused or the queue is empty. * * @return the next worker or {@code null} */ public AnalysisWorker getNextWorker() { synchronized (backgroundQueue) { if (backgroundQueue.isEmpty()) { backgroundJob = null; backgroundQueue.notifyAll(); return null; } return backgroundQueue.remove(0); } } /** * Answer the {@link AnalysisWorker}s in the queue. * * @return an array of workers (not {@code null}, contains no {@code null}s) */ public AnalysisWorker[] getQueueWorkers() { synchronized (backgroundQueue) { return backgroundQueue.toArray(new AnalysisWorker[backgroundQueue.size()]); } } /** * For each queued {@link AnalysisWorker}, remove that worker from the queue and call the * {@link AnalysisWorker#performAnalysis(AnalysisManager)} to perform analysis. Continue until the * queue is empty. This is typically called indirectly on a background thread via * {@link #startBackgroundAnalysis()}. * * @param job The job on which the analysis is performed or {@code null} if none. */ public void performAnalysis(Job job) { while (true) { AnalysisWorker worker; synchronized (backgroundQueue) { worker = getNextWorker(); if (worker == null) { break; } activeWorker = worker; } try { if (job != null) { if (worker.contextManager instanceof Project) { job.setName("Analyzing " + ((Project) worker.contextManager).getResource().getName()); } else if (worker.contextManager instanceof ProjectManager) { job.setName("Analyzing SDK"); } else { job.setName("Analyzing"); } } worker.performAnalysis(this); } finally { synchronized (backgroundQueue) { activeWorker = null; } } } } /** * Ensure that a worker is at the front of the queue to update the analysis for the context. * * @param manager the manager containing the context to be analyzed (not {@code null}) * @param context the context to be analyzed (not {@code null}) * @see #performAnalysis(AnalysisManager) */ public void performAnalysisInBackground(ContextManager manager, AnalysisContext context) { synchronized (backgroundQueue) { if (backgroundQueue.size() == 0 || backgroundQueue.get(0).getContext() != context) { addWorker(new AnalysisWorker(manager, context)); } } } /** * Start a job to perform background analysis if it has not already been started. */ public void startBackgroundAnalysis() { synchronized (backgroundQueue) { if (stopped) { return; } if (backgroundJob == null) { backgroundJob = new Job("Analyzing") { @Override protected IStatus run(IProgressMonitor monitor) { performAnalysis(backgroundJob); return Status.OK_STATUS; } }; backgroundJob.setPriority(Job.BUILD); } backgroundJob.schedule(); } } /** * Stop all background analysis and discard all pending work */ public void stopBackgroundAnalysis() { synchronized (backgroundQueue) { stopped = true; if (activeWorker != null) { activeWorker.stop(); } backgroundQueue.clear(); if (backgroundJob != null) { backgroundJob.cancel(); backgroundJob = null; } backgroundQueue.notifyAll(); } } /** * Wait for any scheduled background analysis to complete or for the specified duration to elapse. * * @param milliseconds the number of milliseconds to wait * @return {@code true} if the background analysis has completed, else {@code false} */ public boolean waitForBackgroundAnalysis(long milliseconds) { synchronized (backgroundQueue) { long end = System.currentTimeMillis() + milliseconds; while (backgroundJob != null) { long delta = end - System.currentTimeMillis(); if (delta <= 0) { return false; } try { backgroundQueue.wait(delta); } catch (InterruptedException e) { //$FALL-THROUGH$ } } return true; } } }