/******************************************************************************* * Copyright (c) 2013 Tasktop Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Tasktop Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.commons.core.operations; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Polls {@link ICancellableOperation} objects for cancellation and aborts the corresponding operations. * * @author Steffen Pingel * @since 3.9 */ public class CancellableOperationMonitorThread extends Thread { private static final int DEFAULT_POLLING_INTERVAL = 1000; private static CancellableOperationMonitorThread instance; public static synchronized CancellableOperationMonitorThread getInstance() { if (instance == null) { instance = new CancellableOperationMonitorThread(); } return instance; } private final List<ICancellableOperation> operations = new CopyOnWriteArrayList<ICancellableOperation>(); private final long pollingInterval; private boolean shutdown; public CancellableOperationMonitorThread() { this(DEFAULT_POLLING_INTERVAL); } public CancellableOperationMonitorThread(long pollingInterval) { this.pollingInterval = pollingInterval; setDaemon(true); } /** * Registers <code>operation</code> to be be monitored for cancellation. If the operation is complete it must be * unregistered by invoking {@link #removeOperation(ICancellableOperation)}. * * @see #removeOperation(ICancellableOperation) */ public synchronized void addOperation(ICancellableOperation operation) { checkShutdown(); operations.add(operation); if (!isAlive() && !shutdown) { start(); } else { notify(); } } /** * Returns the polling interval in milliseconds. */ public long getPollingInterval() { return pollingInterval; } /** * Checks all registered operations for cancellation. Checks all queued operations at most twice. Used for testing. */ public synchronized void processOperations() throws InterruptedException { if (operations.isEmpty()) { throw new IllegalStateException("The list of operations is empty"); //$NON-NLS-1$ } checkShutdown(); notify(); wait(); // ensure processing happens again in case the first notify happened while the queue was processing notify(); wait(); } /** * Unregisters <code>operation</code> to be be monitored for cancellation. * * @see #removeOperation(ICancellableOperation) */ public synchronized void removeOperation(ICancellableOperation operation) { checkShutdown(); operations.remove(operation); } @Override public void run() { try { while (true) { for (ICancellableOperation opertion : operations) { if (opertion.isCanceled()) { opertion.abort(); } } synchronized (this) { // notify threads waiting in processOnce() notifyAll(); // check shutdown flag while holding this if (shutdown) { break; } if (operations.isEmpty()) { wait(); } else { wait(pollingInterval); } } } } catch (InterruptedException e) { // shutdown } } /** * Stops the thread and waits for it to complete. Can be called multiple times. * * @throws InterruptedException * thrown if an interrupted signal is received while waiting for shutdown to complete */ public synchronized void shutdown() throws InterruptedException { this.shutdown = true; notify(); if (isAlive()) { join(); } } /** * Starts the thread. * * @throws IllegalStateException * thrown if the thread was already shutdown * @see #shutdown() */ @Override public synchronized void start() { checkShutdown(); super.start(); } private void checkShutdown() { if (shutdown) { throw new IllegalStateException("Already shutdown"); //$NON-NLS-1$ } } }