/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.core.position.impl;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.position.PortfolioNode;
import com.opengamma.core.position.Position;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.PoolExecutor;
/**
* A traverser that runs in parallel using a number of threads. The ordering is non-deterministic.
*/
public class ParallelPortfolioNodeTraverser extends PortfolioNodeTraverser {
private static final Logger s_logger = LoggerFactory.getLogger(ParallelPortfolioNodeTraverser.class);
private final PoolExecutor _pool;
/**
* Creates a traverser.
*
* @param callback the callback to invoke, not null
* @param executorService the executor service for parallel resolutions
*/
public ParallelPortfolioNodeTraverser(final PortfolioNodeTraversalCallback callback, final PoolExecutor executorService) {
super(callback);
ArgumentChecker.notNull(executorService, "executorService");
_pool = executorService;
}
protected PoolExecutor.Service<?> createExecutorService() {
return _pool.createService(null);
}
private static final class Context {
private final PoolExecutor.Service<?> _executorService;
private final PortfolioNodeTraversalCallback _callback;
public Context(PoolExecutor.Service<?> executorService, PortfolioNodeTraversalCallback callback) {
_executorService = executorService;
_callback = callback;
}
private final class NodeTraverser implements Runnable {
private final NodeTraverser _parent;
private final PortfolioNode _node;
private final AtomicInteger _count = new AtomicInteger();
private volatile boolean _secondPass;
public NodeTraverser(final PortfolioNode node, final NodeTraverser parent) {
_node = node;
_parent = parent;
}
@Override
public void run() {
_callback.preOrderOperation(_node);
final List<PortfolioNode> childNodes = _node.getChildNodes();
final List<Position> positions = _node.getPositions();
_count.addAndGet(childNodes.size() + positions.size());
for (final Position position : positions) {
submit(new Runnable() {
@Override
public void run() {
try {
_callback.preOrderOperation(_node, position);
} catch (Exception e) {
s_logger.warn("Failed preOrderOperation", e);
} finally {
childDone();
}
}
});
}
for (final PortfolioNode node : childNodes) {
submit(new NodeTraverser(node, this));
}
}
public void childDone() {
if (_count.decrementAndGet() == 0) {
if (_secondPass) {
try {
_callback.postOrderOperation(_node);
} catch (Exception e) {
s_logger.warn("Failed preOrderOperation", e);
} finally {
if (_parent != null) {
_parent.childDone();
}
}
} else {
_secondPass = true;
final List<Position> positions = _node.getPositions();
if (positions.isEmpty()) {
try {
_callback.postOrderOperation(_node);
} catch (Exception e) {
s_logger.warn("Failed postOrderOperation", e);
} finally {
if (_parent != null) {
_parent.childDone();
}
}
} else {
_count.addAndGet(positions.size());
for (final Position position : positions) {
submit(new Runnable() {
@Override
public void run() {
try {
_callback.postOrderOperation(_node, position);
} catch (Exception e) {
s_logger.warn("Failed postOrderOperation", e);
} finally {
childDone();
}
}
});
}
}
}
}
}
}
private void submit(final Runnable runnable) {
_executorService.execute(runnable);
}
public void waitForCompletion() {
try {
_executorService.join();
} catch (InterruptedException e) {
s_logger.info("Interrupted waiting for completion");
throw new OpenGammaRuntimeException("interrupted", e);
}
}
}
/**
* Traverse the nodes notifying using the callback.
*
* @param portfolioNode the node to start from, null does nothing
*/
@Override
public void traverse(final PortfolioNode portfolioNode) {
if (portfolioNode == null) {
return;
}
final Context context = new Context(createExecutorService(), getCallback());
context.submit(context.new NodeTraverser(portfolioNode, null));
context.waitForCompletion();
}
}