/*
* Copyright (C) 2015 drrb
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.drrb.rust.netbeans.commandrunner;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class CommandFuture {
private final ExecutorService eventThread = Executors.newSingleThreadExecutor();
protected final BlockingQueue<Event> eventQueue = new LinkedBlockingQueue<>();
private final List<Listener> listeners = new LinkedList<>();
private final List<String> lines = new LinkedList<>();
private volatile boolean started = false;
private volatile boolean finished = false;
public static class Listener {
public void onStart() {
}
public void onLinePrinted(String line) {
}
public void onFinish() {
}
}
public InputStream wrap(InputStream delegate) {
startEventThread();
return new CommandInputStream(delegate);
}
public void addListener(Listener listener) {
ListenerAdded event = new ListenerAdded(listener);
if (finished) {
event.process();
} else {
registerEvent(event);
}
}
@VisibleForTesting
void start() {
registerEvent(new CommandStarted());
}
@VisibleForTesting
void printLine(String line) {
registerEvent(new LinePrinted(line));
}
@VisibleForTesting
void finish() {
registerEvent(new CommandFinished());
}
@VisibleForTesting
protected void startEventThread() {
eventThread.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
processEvents(); return null;
}
});
}
@VisibleForTesting
protected void processEvents() throws InterruptedException {
while (true) {
Event nextEvent = eventQueue.take();
nextEvent.process();
if (finished) {
eventThread.shutdown();
return;
}
}
}
private void registerEvent(Event event) {
eventQueue.add(event);
}
@VisibleForTesting
protected interface Event {
void process();
}
private class ListenerAdded implements Event {
private final Listener listener;
public ListenerAdded(Listener listener) {
this.listener = listener;
}
@Override
public void process() {
if (started) {
listener.onStart();
}
for (String line : lines) {
listener.onLinePrinted(line);
}
if (finished) {
listener.onFinish();
}
listeners.add(listener);
}
}
private class CommandStarted implements Event {
@Override
public void process() {
started = true;
for (Listener listener : listeners) {
listener.onStart();
}
}
}
private class LinePrinted implements Event {
private final String line;
public LinePrinted(String line) {
this.line = line;
}
@Override
public void process() {
lines.add(line);
for (Listener listener : listeners) {
listener.onLinePrinted(line);
}
}
}
private class CommandFinished implements Event {
@Override
public void process() {
finished = true;
for (Listener listener : listeners) {
listener.onFinish();
}
}
}
private class CommandInputStream extends InputStream {
private final InputStream delegate;
private final ByteBuffer lineBuffer = ByteBuffer.allocate(1024);
public CommandInputStream(InputStream delegate) {
this.delegate = delegate;
}
@Override
public int read() throws IOException {
if (!started) {
start();
}
int nextByte = delegate.read();
if (nextByte == '\n') {
printLine(consumeCurrentLine());
} else if (nextByte == -1 && !finished) {
finish();
} else {
appendToCurrentLine(nextByte);
}
return nextByte;
}
private void appendToCurrentLine(int aByte) {
lineBuffer.put((byte) aByte);
}
private String consumeCurrentLine() {
lineBuffer.flip();
try {
return UTF_8.decode(lineBuffer).toString();
} finally {
lineBuffer.clear();
}
}
}
}