/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.launcher.daemon.client;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.concurrent.ExecutorFactory;
import org.gradle.internal.concurrent.Stoppable;
import org.gradle.internal.concurrent.StoppableExecutor;
import org.gradle.internal.io.TextStream;
import org.gradle.util.DisconnectableInputStream;
import org.gradle.internal.io.LineBufferingOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.AsynchronousCloseException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Asynchronously consumes from an input stream for a time,
* forwarding a <strong>line</strong> of input at a time to a specified action.
*
* Note that calling stop() will NOT close the source input stream.
*/
public class InputForwarder implements Stoppable {
private final InputStream input;
private final TextStream handler;
private final ExecutorFactory executorFactory;
private final int bufferSize;
private StoppableExecutor forwardingExecuter;
private DisconnectableInputStream disconnectableInput;
private LineBufferingOutputStream outputBuffer;
private final Lock lifecycleLock = new ReentrantLock();
private boolean started;
private boolean stopped;
public InputForwarder(InputStream input, TextStream handler, ExecutorFactory executerFactory, int bufferSize) {
this.input = input;
this.handler = handler;
this.executorFactory = executerFactory;
this.bufferSize = bufferSize;
}
public InputForwarder start() {
lifecycleLock.lock();
try {
if (started) {
throw new IllegalStateException("input forwarder has already been started");
}
disconnectableInput = new DisconnectableInputStream(input, bufferSize);
outputBuffer = new LineBufferingOutputStream(handler, bufferSize);
forwardingExecuter = executorFactory.create("forward input");
forwardingExecuter.execute(new Runnable() {
public void run() {
byte[] buffer = new byte[bufferSize];
int readCount;
Throwable readFailure = null;
try {
while (true) {
try {
readCount = disconnectableInput.read(buffer, 0, bufferSize);
if (readCount < 0) {
break;
}
} catch (AsynchronousCloseException e) {
break;
} catch (IOException e) {
readFailure = e;
break;
}
outputBuffer.write(buffer, 0, readCount);
}
outputBuffer.flush(); // will flush any unterminated lines out synchronously
} catch(IOException e) {
// should not happen
throw UncheckedException.throwAsUncheckedException(e);
} finally {
handler.endOfStream(readFailure);
}
}
});
started = true;
} finally {
lifecycleLock.unlock();
}
return this;
}
public void stop() {
lifecycleLock.lock();
try {
if (!stopped) {
try {
disconnectableInput.close();
} catch (IOException e) {
throw UncheckedException.throwAsUncheckedException(e);
}
forwardingExecuter.stop();
stopped = true;
}
} finally {
lifecycleLock.unlock();
}
}
}