| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.tests.resources.usecase; |
| |
| import org.eclipse.core.internal.resources.ResourceStatus; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.runtime.*; |
| |
| /** |
| * A class to help testing use cases that access the workspace concurrently but in |
| * a deterministic way. |
| * Operations can be defined with some sync points. It means that the operation will |
| * run to that point and wait for some other thread signal (call #proceed) before continuing. |
| * |
| * Some of the constructs can lead to deadlocks if not used correctly. |
| * |
| * One tip to avoid deadlocks is aways honour the sync points defined by the operation. |
| * For example, exceptions should be logged using #logException instead of thrown. |
| * |
| * Although operations of this type can run using IWorkspace.run(), it was designed |
| * to run in a separate thread. |
| * Example: new Thread(concurrentOperation).start(); |
| */ |
| public abstract class ConcurrentOperation implements Runnable, IWorkspaceRunnable { |
| /** workspace */ |
| protected IWorkspace workspace; |
| |
| /** synchronization flags */ |
| protected boolean go; |
| protected boolean isWaiting; |
| protected int hasStarted; |
| |
| /** log any exception we get */ |
| protected MultiStatus status; |
| |
| /** locks */ |
| protected Object startedLock = new Object(); |
| |
| /** constants */ |
| protected static final int STARTED_NONE = 0; |
| protected static final int STARTED_YES = 1; |
| protected static final int STARTED_NO = 2; |
| |
| public ConcurrentOperation(IWorkspace workspace) { |
| this.workspace = workspace; |
| reset(); |
| } |
| |
| /** |
| * This method should verify all pre-requisites necessaries in order |
| * to the operation run properly. |
| */ |
| abstract protected void assertRequisites() throws Exception; |
| |
| public IStatus getStatus() { |
| return status; |
| } |
| |
| /** |
| * Returns only when we get out of the STARTED_NONE state. |
| */ |
| public boolean hasStarted() { |
| |
| synchronized (startedLock) { |
| while (hasStarted == STARTED_NONE) { |
| try { |
| startedLock.wait(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| } |
| return hasStarted == STARTED_YES; |
| } |
| |
| protected boolean isReadyToStart() { |
| boolean ok = true; |
| try { |
| assertRequisites(); |
| } catch (Exception e) { |
| logException(e); |
| ok = false; |
| } |
| return ok; |
| } |
| |
| protected void logException(Exception e) { |
| if (e instanceof CoreException) { |
| status.add(((CoreException) e).getStatus()); |
| } else { |
| status.add(new ResourceStatus(0, null, null, e)); |
| } |
| } |
| |
| /** |
| * @see #waitNotification |
| */ |
| public synchronized void proceed() { |
| go = true; |
| notify(); |
| } |
| |
| public void reset() { |
| go = false; |
| isWaiting = false; |
| hasStarted = STARTED_NONE; |
| status = new MultiStatus("a plugin", IStatus.INFO, "", null); |
| } |
| |
| /** |
| * Only returns from this method if the operation is in |
| * a sync point. |
| * |
| * This method can cause deadlock. |
| */ |
| public synchronized void returnWhenInSyncPoint() { |
| while (!isWaiting && hasStarted()) { |
| try { |
| wait(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| } |
| |
| @Override |
| public void run() { |
| |
| if (isReadyToStart()) { |
| setHasStarted(true); |
| try { |
| workspace.run(this, null); |
| } catch (Exception e) { |
| logException(e); |
| } |
| } else { |
| setHasStarted(false); |
| } |
| |
| } |
| |
| protected void setHasStarted(boolean value) { |
| |
| synchronized (startedLock) { |
| hasStarted = value ? STARTED_YES : STARTED_NO; |
| startedLock.notify(); |
| } |
| } |
| |
| /** |
| * Waits until #proceed is called. |
| * |
| * This method can cause deadlock. |
| */ |
| protected synchronized void syncPoint() { |
| /* we set "go" to false because we want to continue only when proceed is called */ |
| go = false; |
| isWaiting = true; |
| notify(); // notify we are in a sync point |
| while (!go) { |
| try { |
| wait(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| isWaiting = false; |
| } |
| } |