/**
*
*/
package org.signalml.plugin.newartifact.logic.mgr;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import org.signalml.domain.signal.samplesource.MultichannelSampleSource;
import org.signalml.method.ComputationException;
import org.signalml.plugin.data.logic.PluginComputationMgrStepResult;
import org.signalml.plugin.exception.PluginToolAbortException;
import org.signalml.plugin.exception.PluginToolInterruptedException;
import org.signalml.plugin.export.SignalMLException;
import org.signalml.plugin.io.IPluginDataSourceReader;
import org.signalml.plugin.method.helper.AbstractPluginTrackerUpdaterWithTimer;
import org.signalml.plugin.method.logic.AbstractPluginComputationMgrStep;
import org.signalml.plugin.method.logic.IPluginComputationMgrStepTrackerProxy;
import org.signalml.plugin.method.logic.PluginWorkerSet;
import org.signalml.plugin.newartifact.data.INewArtifactSignalReaderWorkerData;
import org.signalml.plugin.newartifact.data.NewArtifactAlgorithmWorkerData;
import org.signalml.plugin.newartifact.data.NewArtifactComputationType;
import org.signalml.plugin.newartifact.data.NewArtifactConstants;
import org.signalml.plugin.newartifact.data.mgr.NewArtifactMgrStepData;
import org.signalml.plugin.newartifact.data.mgr.NewArtifactMgrStepResult;
import org.signalml.plugin.newartifact.io.INewArtifactAlgorithmWriter;
import org.signalml.plugin.newartifact.io.NewArtifactAlgorithmWriter;
import org.signalml.plugin.newartifact.io.NewArtifactDoubleFileAlgorithmWriter;
import org.signalml.plugin.newartifact.io.NewArtifactSignalReaderWorker;
import org.signalml.plugin.signal.PluginSignalHelper;
/**
* @author kdr
*
*/
public class NewArtifactMgrPreprocessStep extends
AbstractPluginComputationMgrStep<NewArtifactMgrStepData> {
private final int INPUT_BUFFER_QUEUE_SIZE = 40;
private PluginWorkerSet workers;
private Collection<INewArtifactAlgorithmWriter> writers;
private INewArtifactAlgorithmBufferSynchronizer synchronizer;
private final BlockingQueue<double[][]> readyBuffersQueue;
private final BlockingQueue<double[][]> freeBuffersQueue;
private final AtomicBoolean shutdownFlag;
private AbstractPluginTrackerUpdaterWithTimer trackerUpdater;
private Integer blockCount;
private interface INewArtifactAlgorithmBufferSynchronizer {
public void addBuffer(double buffer[][]);
public double[][] getNextBufferForObject(Object sender)
throws InterruptedException;
public void markCurrentBufferAsProcessed(Object sender);
}
private class NewArtifactAlgorithmBufferSynchronizer implements
INewArtifactAlgorithmBufferSynchronizer {
private final List<double[][]> bufferList = new LinkedList<double[][]>();
private final Map<double[][], Integer> useCount = new HashMap<double[][], Integer>();
private final Map<Object, Integer> nextBuffer = new HashMap<Object, Integer>();
private final Map<Object, Semaphore> waitSemaphore = new HashMap<Object, Semaphore>();
private final BlockingQueue<double[][]> freeBuffersQueue;
private final AtomicBoolean shutdownFlag;
public NewArtifactAlgorithmBufferSynchronizer(
BlockingQueue<double[][]> freeBuffersQueue,
AtomicBoolean shutdownFlag) {
this.freeBuffersQueue = freeBuffersQueue;
this.shutdownFlag = shutdownFlag;
}
@Override
public void addBuffer(double buffer[][]) {
synchronized (this) {
bufferList.add(buffer);
for (Semaphore s : waitSemaphore.values()) {
s.release();
}
useCount.put(buffer, waitSemaphore.size());
}
}
@Override
public double[][] getNextBufferForObject(Object sender)
throws InterruptedException {
final Semaphore s;
synchronized (this) {
if (!waitSemaphore.containsKey(sender)) {
waitSemaphore.put(sender, new Semaphore(bufferList.size()));
nextBuffer.put(sender, 0);
for (double[][] buffer : bufferList) {
useCount.put(buffer, useCount.get(buffer) + 1);
}
}
s = waitSemaphore.get(sender);
}
s.acquire();
synchronized (this) {
Integer index = nextBuffer.get(sender);
double buffer[][] = bufferList.get(index);
if (buffer == null || buffer.length == 0) {
s.release();
return null;
}
nextBuffer.put(sender, index + 1);
return buffer;
}
}
@Override
public void markCurrentBufferAsProcessed(Object sender) {
double buffer[][];
Integer count;
synchronized (this) {
Integer index = nextBuffer.get(sender);
if (index == null) {
return;
}
buffer = bufferList.get(index - 1);
count = useCount.get(buffer);
if (count == null) {
return;
}
if (count == 1) {
useCount.remove(buffer);
bufferList.remove(index - 1);
for (Entry<Object, Integer> entry : nextBuffer.entrySet()) {
entry.setValue(entry.getValue() - 1);
}
} else {
useCount.put(buffer, count - 1);
}
}
if (count == 1 && !this.shutdownFlag.get()) {
try {
this.freeBuffersQueue.put(buffer);
} catch (InterruptedException e) {
return;
}
}
}
}
private class NewArtifactAlgorithmDataSource implements
IPluginDataSourceReader {
private INewArtifactAlgorithmBufferSynchronizer synchronizer;
private double currentBuffer[][];
public NewArtifactAlgorithmDataSource(
INewArtifactAlgorithmBufferSynchronizer synchronizer) {
this.synchronizer = synchronizer;
this.currentBuffer = null;
}
@Override
public void getSample(double[][] buffer) throws InterruptedException {
if (this.currentBuffer == null) {
this.fetchNextBuffer();
}
if (this.currentBuffer != null) {
this.copyChunk(buffer);
this.synchronizer.markCurrentBufferAsProcessed(this);
this.currentBuffer = null;
}
}
@Override
public boolean hasMoreSamples() throws InterruptedException {
if (this.currentBuffer == null) {
this.fetchNextBuffer();
}
return this.currentBuffer != null;
}
private void fetchNextBuffer() throws InterruptedException {
this.currentBuffer = this.synchronizer.getNextBufferForObject(this);
}
private void copyChunk(double buffer[][]) {
for (int i = 0; i < buffer.length; ++i) {
buffer[i] = Arrays.copyOf(this.currentBuffer[i],
buffer[i].length); // TODO
}
}
}
private class NewArtifactSignalReaderWorkerData implements
INewArtifactSignalReaderWorkerData {
private BlockingQueue<double[][]> inputQueue;
private BlockingQueue<double[][]> outputQueue;
private MultichannelSampleSource source;
private NewArtifactConstants constants;
public NewArtifactSignalReaderWorkerData(
MultichannelSampleSource source,
NewArtifactConstants constants,
BlockingQueue<double[][]> inputQueue,
BlockingQueue<double[][]> outputQueue) {
this.source = source;
this.constants = constants;
this.inputQueue = inputQueue;
this.outputQueue = outputQueue;
}
@Override
public double[][] getWritableBuffer() throws InterruptedException {
return this.inputQueue.take();
}
@Override
public void markBufferAsReady(double[][] buffer)
throws InterruptedException {
this.outputQueue.put(buffer);
}
@Override
public void finalizeBuffers() throws InterruptedException {
this.outputQueue.put(new double[0][]);
}
@Override
public MultichannelSampleSource getSignalSource() {
return this.source;
}
@Override
public NewArtifactConstants getArtifactConstants() {
return this.constants;
}
}
private class TrackerUpdater extends AbstractPluginTrackerUpdaterWithTimer {
private final IPluginComputationMgrStepTrackerProxy<NewArtifactComputationProgressPhase> tracker;
private final NewArtifactMgrPreprocessStep step;
private final int blockCount;
public TrackerUpdater(
NewArtifactMgrPreprocessStep step,
IPluginComputationMgrStepTrackerProxy<NewArtifactComputationProgressPhase> tracker,
int blockCount) {
this.step = step;
this.tracker = tracker;
this.blockCount = blockCount;
}
@Override
public void update(int progress, int prevProgress) {
this.tracker.advance(this.step,
Math.max(progress - prevProgress, 0));
this.tracker
.setProgressPhase(
NewArtifactComputationProgressPhase.INTERMEDIATE_COMPUTATION_PHASE,
progress, this.blockCount);
}
}
public NewArtifactMgrPreprocessStep(NewArtifactMgrStepData data) {
super(data);
this.blockCount = null;
this.freeBuffersQueue = new ArrayBlockingQueue<double[][]>(
this.INPUT_BUFFER_QUEUE_SIZE);
this.readyBuffersQueue = new ArrayBlockingQueue<double[][]>(
this.INPUT_BUFFER_QUEUE_SIZE);
this.shutdownFlag = new AtomicBoolean(false);
this.workers = new PluginWorkerSet(this.data.threadFactory);
this.writers = new LinkedList<INewArtifactAlgorithmWriter>();
this.trackerUpdater = new TrackerUpdater(this, this.data.tracker,
this.getBlockCount());
}
@Override
public int getStepNumberEstimate() {
return this.getBlockCount() + 1;
}
@Override
public PluginComputationMgrStepResult doRun(
PluginComputationMgrStepResult prevStepResult)
throws ComputationException, PluginToolInterruptedException,
PluginToolAbortException {
final IPluginComputationMgrStepTrackerProxy<NewArtifactComputationProgressPhase> tracker = this.data.tracker;
this.checkAbortState();
tracker.setProgressPhase(NewArtifactComputationProgressPhase.PREPROCESS_PREPARE_PHASE);
this.prepareWorkers();
this.checkAbortState();
tracker.advance(this, 1);
int current = 0;
this.trackerUpdater.start(1500);
this.workers.startAll();
this.checkAbortState();
tracker.setProgressPhase(NewArtifactComputationProgressPhase.SOURCE_FILE_INITIAL_READ_PHASE);
double buffer[][] = null;
do {
try {
buffer = this.readyBuffersQueue.take();
} catch (InterruptedException e) {
throw new PluginToolInterruptedException(e);
}
this.synchronizer.addBuffer(buffer);
current++;
if (current % 100 == 0) {
this.checkAbortState();
}
this.trackerUpdater.setProgress(current);
} while (buffer != null && buffer.length != 0);
this.checkAbortState();
return this.prepareStepResult();
}
private void prepareWorkers() throws ComputationException {
int channelCount = this.data.artifactData.getSampleSource()
.getChannelCount();
int blockLength = this.data.constants.getBlockLengthWithPadding();
double[][][] inputBuffer = new double[this.INPUT_BUFFER_QUEUE_SIZE][channelCount][blockLength];
for (double[][] buffer : inputBuffer) {
this.freeBuffersQueue.add(buffer);
}
this.synchronizer = new NewArtifactAlgorithmBufferSynchronizer(
this.freeBuffersQueue, this.shutdownFlag);
NewArtifactSignalReaderWorker reader = new NewArtifactSignalReaderWorker(
new NewArtifactSignalReaderWorkerData(
this.data.artifactData.getSampleSource(),
this.data.constants, this.freeBuffersQueue,
this.readyBuffersQueue));
for (NewArtifactComputationType algorithmType : NewArtifactComputationType
.values()) {
INewArtifactAlgorithmWriter writer = this
.createResultWriterForAlgorithm(algorithmType);
if (writer != null) {
this.writers.add(writer);
this.workers.add(new NewArtifactAlgorithmWorker(
new NewArtifactAlgorithmDataSource(synchronizer),
new NewArtifactAlgorithmFactory(algorithmType,
this.data.constants), writer,
new NewArtifactAlgorithmWorkerData(
this.data.artifactData, this.data.constants)));
}
}
this.workers.add(reader);
this.trackerUpdater = new TrackerUpdater(this, this.data.tracker,
this.getBlockCount());
}
@Override
protected NewArtifactMgrStepResult prepareStepResult() {
return new NewArtifactMgrStepResult(this.getClass());
}
@Override
protected void cleanup() {
super.cleanup();
this.trackerUpdater.stop();
this.stopWorkers();
}
private void stopWorkers() {
try {
if (this.synchronizer != null) {
this.synchronizer.addBuffer(null);
}
this.shutdownFlag.set(true);
this.freeBuffersQueue.clear();
this.freeBuffersQueue.add(new double[0][]);
this.trackerUpdater.stop();
this.data.tracker
.setProgressPhase(
NewArtifactComputationProgressPhase.INTERMEDIATE_COMPUTATION_PHASE,
this.getBlockCount(), this.getBlockCount());
int advance = this.getBlockCount()
- this.trackerUpdater.getProgress() + 1;
if (advance > 0) {
this.data.tracker.advance(this, advance);
}
} finally {
this.terminateWorkers();
}
}
private void terminateWorkers() {
this.workers.terminateAll();
for (INewArtifactAlgorithmWriter writer : this.writers) {
try {
writer.close();
} catch (IOException e) {
}
}
this.writers.clear();
}
private INewArtifactAlgorithmWriter createResultWriterForAlgorithm(
NewArtifactComputationType algorithmType)
throws ComputationException {
if (!NewArtifactParameterHelper.IsParameterEnabled(algorithmType,
this.data.artifactData)) {
return null;
}
String fileNames[] = this.data.pathConstructor
.getIntermediateFileNamesForAlgorithm(algorithmType);
String workDirName = this.data.pathConstructor.getPathToWorkDir();
try {
switch (algorithmType) {
case MUSCLE_PLUS_POWER:
assert fileNames.length == 2;
return new NewArtifactDoubleFileAlgorithmWriter(new File(
workDirName, fileNames[0]), new File(workDirName,
fileNames[1]));
case MUSCLE_ACTIVITY:
case POWER:
return null;
case EYE_MOVEMENT:
case TECHNICAL:
case ECG:
case EYEBLINKS:
case UNKNOWN:
default:
assert fileNames.length == 1;
return new NewArtifactAlgorithmWriter(new File(workDirName,
fileNames[0]));
}
} catch (SignalMLException e) {
throw new ComputationException(e);
}
}
private int getBlockCount() {
if (this.blockCount == null) {
this.blockCount = new Integer(PluginSignalHelper.GetBlockCount(
this.data.artifactData.getSampleSource(),
this.data.constants.getBlockLength()));
}
return this.blockCount;
}
}