/* * 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.engine.internal.index.operation; import com.google.common.collect.Sets; import com.google.common.util.concurrent.Uninterruptibles; import com.google.dart.engine.AnalysisEngine; import com.google.dart.engine.source.Source; import com.google.dart.engine.utilities.translation.DartOmit; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Instances of the {@link OperationProcessor} process the operations on a single * {@link OperationQueue operation queue}. Each processor can be run one time on a single thread. * * @coverage dart.engine.index */ @DartOmit public class OperationProcessor { /** * The enumeration <code>ProcessorState</code> represents the possible states of an operation * processor. */ private enum ProcessorState { /** * The processor is ready to be run (has not been run before). */ READY, /** * The processor is currently performing operations. */ RUNNING, /** * The processor is currently performing operations but has been asked to stop. */ STOP_REQESTED, /** * The processor has stopped performing operations and cannot be used again. */ STOPPED; } /** * The queue containing the operations to be processed. */ private OperationQueue queue; /** * The current state of the processor. */ private ProcessorState state = ProcessorState.READY; /** * The number of milliseconds for which the thread on which the processor is running will wait for * an operation to become available if there are no operations ready to be processed. */ private static long WAIT_DURATION = 100L; /** * Initialize a newly created operation processor to process the operations on the given queue. * * @param queue the queue containing the operations to be processed */ public OperationProcessor(OperationQueue queue) { this.queue = queue; } /** * Start processing operations. If the processor is already running on a different thread, then * this method will return immediately with no effect. Otherwise, this method will not return * until after the processor has been stopped from a different thread or until the thread running * the processor has been interrupted. */ public void run() { synchronized (this) { // This processor is, or was, already running on a different thread. if (state != ProcessorState.READY) { throw new IllegalStateException("Operation processors can only be run one time: " + state); //$NON-NLS-1$ } // OK, run. state = ProcessorState.RUNNING; } try { while (isRunning()) { // wait for operation IndexOperation operation = null; try { operation = queue.dequeue(WAIT_DURATION); } catch (InterruptedException exception) { // ignore } // perform operation if (operation != null) { try { operation.performOperation(); } catch (Throwable exception) { AnalysisEngine.getInstance().getLogger().logError( "Exception in indexing operation: " + operation, exception); //$NON-NLS-1$ } } } } finally { synchronized (this) { state = ProcessorState.STOPPED; } } } /** * Stop processing operations after the current operation has completed. If the argument is * {@code true} then this method will wait until the last operation has completed; otherwise this * method might return before the last operation has completed. * * @param wait {@code true} if this method will wait until the last operation has completed before * returning * @return the library files for the libraries that need to be analyzed when a new session is * started. */ public Source[] stop(boolean wait) { synchronized (this) { if (state == ProcessorState.READY) { state = ProcessorState.STOPPED; return getUnanalyzedSources(); } else if (state == ProcessorState.STOPPED) { return getUnanalyzedSources(); } else if (state == ProcessorState.RUNNING) { state = ProcessorState.STOP_REQESTED; } } while (wait) { synchronized (this) { if (state == ProcessorState.STOPPED) { return getUnanalyzedSources(); } } waitOneMs(); } return getUnanalyzedSources(); } /** * Waits until processors will switch from "ready" to "running" state. * * @return {@code true} if processor is now actually in "running" state, e.g. not in "stopped" * state. */ public boolean waitForRunning() { while (state == ProcessorState.READY) { threadYield(); } return state == ProcessorState.RUNNING; } /** * @return the {@link Source}s that are not indexed yet. */ private Source[] getUnanalyzedSources() { Set<Source> sources = Sets.newHashSet(); for (IndexOperation operation : queue.getOperations()) { if (operation instanceof IndexUnitOperation) { Source source = ((IndexUnitOperation) operation).getSource(); sources.add(source); } } return sources.toArray(new Source[sources.size()]); } /** * Return {@code true} if the current state is {@link ProcessorState#RUNNING}. * * @return {@code true} if this processor is running */ private boolean isRunning() { synchronized (this) { return state == ProcessorState.RUNNING; } } private void threadYield() { Thread.yield(); } private void waitOneMs() { Uninterruptibles.sleepUninterruptibly(1, TimeUnit.MILLISECONDS); } }