/*******************************************************************************
* Copyright (c) 2006, 2015 Wind River Systems 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.dsf.concurrent;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.core.runtime.MultiStatus;
/**
* Utility class to collect multiple request monitor results of commands
* that are initiated simultaneously. The usage is as follows:
* <pre>
* final MultiRequestMonitor multiRequestMon = new MultiRequestMonitor(fExecutor, null) {
* public void handleCompleted() {
* System.out.println("All complete, errors=" + !getStatus().isOK());
* }
* };
* multiReqMon.requireDoneAdding();
*
* for (int i = 0; i < 10; i++) {
* service.call(i, multiRequestMon.add(
* new RequestMonitor(fExecutor, null) {
* public void handleCompleted() {
* System.out.println(Integer.toString(i) + " complete");
* multiRequestMon.requestMonitorDone(this);
* }
* }));
* }
*
* multiReqMon.doneAdding();
* </pre>
*
* @since 1.0
*/
public class MultiRequestMonitor<V extends RequestMonitor> extends RequestMonitor {
private List<V> fRequestMonitorList = Collections.synchronizedList(new LinkedList<V>());
private Map<V,Boolean> fStatusMap = Collections.synchronizedMap(new HashMap<V,Boolean>());
private int fDoneCounter;
/**
* Has client called {@link #requireDoneAdding()}? Should be set right
* after construction so no need to synchronize
*/
private boolean fRequiresDoneAdding;
/**
* Has client called {@link #doneAdding()}?
*/
private boolean fDoneAdding;
public MultiRequestMonitor(Executor executor, RequestMonitor parentRequestMonitor) {
super(executor, parentRequestMonitor);
setStatus(new MultiStatus(DsfPlugin.PLUGIN_ID, 0, "Collective status for set of sub-operations.", null)); //$NON-NLS-1$
}
/**
* Adds a new RequestMonitor callback to this tracker's list.
* @param <T> Client-specific class of the RequestMonitor callback, it's used here to avoid an
* unnecessary cast by the client.
* @param rm Request monitor object to add to the tracker
* @return The request monitor that was just added, it allows this method to be used
* in-lined in service method calls
*/
public synchronized <T extends V> T add(T rm) {
assert !fStatusMap.containsKey(rm);
if (fDoneAdding) {
throw new IllegalStateException("Can't add a monitor after having called doneAdding()"); //$NON-NLS-1$
}
fRequestMonitorList.add(rm);
fStatusMap.put(rm, false);
fDoneCounter++;
return rm;
}
/**
* Marks the given RequestMonitor callback as completed. Client implementations of
* the RequestMonitor callback have to call this method in order for the tracker
* to complete.
* <br>
* @param requestMonitor
*/
public void requestMonitorDone(V requestMonitor) {
// Avoid holding object lock while calling our super's done()
boolean callSuperDone = false;
synchronized (this) {
if (getStatus() instanceof MultiStatus) {
((MultiStatus)getStatus()).merge(requestMonitor.getStatus());
}
if (!fStatusMap.containsKey(requestMonitor)) {
throw new IllegalArgumentException("Unknown monitor."); //$NON-NLS-1$
}
fStatusMap.put(requestMonitor, true);
assert fDoneCounter > 0;
fDoneCounter--;
if (fDoneCounter == 0 && (fDoneAdding || !fRequiresDoneAdding)) {
assert !fStatusMap.containsValue(false);
callSuperDone = true;
}
}
if (callSuperDone) {
super.done();
}
}
/**
* Returns the list of requested monitors, sorted in order as they were
* added. The returned list is a copy.
*/
public List<V> getRequestMonitors() {
synchronized (fRequestMonitorList) { // needed while copying, even when list is a synchronized collection
return new LinkedList<V>(fRequestMonitorList);
}
}
/**
* Returns true if given monitor is finished.
*/
public boolean isRequestMonitorDone(V rm) {
return fStatusMap.get(rm);
}
/**
* In order to avoid a theoretical (but unlikely) race condition failure,
* clients should call this method immediately after creating the monitor.
* Doing so will require the client to use {@link #doneAdding()} to indicate
* it has finished adding monitors via {@link #add(RequestMonitor)}
*
* @since 2.1
*/
public void requireDoneAdding() {
fRequiresDoneAdding = true;
}
/**
* Client should call this after it has finished adding monitors to this
* MultiRequestMonitor. The client must have first called
* {@link #requireDoneAdding()}, otherwise an {@link IllegalStateException}
* is thrown
*
* @since 2.1
*/
public void doneAdding() {
// Avoid holding object lock while calling our super's done().
boolean callSuperDone = false;
synchronized (this) {
if (!fRequiresDoneAdding) {
throw new IllegalStateException("The method requiresDoneAdding() must be called first"); //$NON-NLS-1$
}
fDoneAdding = true;
// In theory, the added monitors may have already completed.
if (fDoneCounter == 0) {
assert !fStatusMap.containsValue(false);
callSuperDone = true;
}
}
if (callSuperDone) {
super.done();
}
}
@Override
public String toString() {
return "Multi-RequestMonitor: " + getStatus().toString(); //$NON-NLS-1$
}
}