/*
* Copyright 2015-present Facebook, Inc.
*
* 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 com.facebook.buck.util;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
/**
* Implementation of {@link ListeningProcessExecutor.ProcessListener} which decodes bytes to and
* from Java String data.
*/
public abstract class AbstractCharsetProcessListener
implements ListeningProcessExecutor.ProcessListener {
private final ListeningCharsetEncoder stdinEncoder;
private final ListeningCharsetDecoder stdoutDecoder;
private final ListeningCharsetDecoder stderrDecoder;
private class StdinEncoderListener implements ListeningCharsetEncoder.EncoderListener {
@Override
public boolean onStdinReady(CharBuffer buffer) {
return onStdinCharsReady(buffer);
}
@Override
public void onEncoderError(CoderResult result) {
onStdinEncoderError(result);
}
}
private class StdoutDecoderListener implements ListeningCharsetDecoder.DecoderListener {
@Override
public void onDecode(CharBuffer buffer, boolean closed, CoderResult decoderResult) {
onStdoutChars(buffer, closed, decoderResult);
}
}
private class StderrDecoderListener implements ListeningCharsetDecoder.DecoderListener {
@Override
public void onDecode(CharBuffer buffer, boolean closed, CoderResult decoderResult) {
onStderrChars(buffer, closed, decoderResult);
}
}
/**
* Creates a {@link AbstractCharsetProcessListener} which uses a single {@link Charset} to encode
* and decode stdin, stdout, and stderr bytes to and from String data.
*/
public AbstractCharsetProcessListener(Charset charset) {
this(charset.newEncoder(), charset.newDecoder(), charset.newDecoder());
}
/**
* Creates a {@link AbstractCharsetProcessListener} which uses separate {@link CharsetEncoder} and
* {@link CharsetDecoder}s to encode and decode stdin, stdout, and stderr bytes to and from String
* data.
*/
public AbstractCharsetProcessListener(
CharsetEncoder stdinEncoder, CharsetDecoder stdoutDecoder, CharsetDecoder stderrDecoder) {
this.stdinEncoder = new ListeningCharsetEncoder(new StdinEncoderListener(), stdinEncoder);
this.stdoutDecoder = new ListeningCharsetDecoder(new StdoutDecoderListener(), stdoutDecoder);
this.stderrDecoder = new ListeningCharsetDecoder(new StderrDecoderListener(), stderrDecoder);
}
@Override
public void onStart(ListeningProcessExecutor.LaunchedProcess process) {}
@Override
public void onExit(int exitCode) {}
@Override
public final void onStdout(ByteBuffer buffer, boolean closed) {
this.stdoutDecoder.onOutput(buffer, closed);
}
@Override
public final void onStderr(ByteBuffer buffer, boolean closed) {
this.stderrDecoder.onOutput(buffer, closed);
}
@Override
public boolean onStdinReady(ByteBuffer buffer) {
return this.stdinEncoder.onStdinReady(buffer);
}
/**
* Called when the process is ready to receive string data on stdin.
*
* <p>Before this method returns, you must set the {@code buffer}'s {@link CharBuffer#position()
* position} and {@link CharBuffer#limit() limit} (for example, by invoking {@link
* CharBuffer#flip()}) to indicate how much data is in the buffer before returning from this
* method.
*
* <p>You must first call {@link ListeningProcessExecutor.LaunchedProcess#wantWrite()} at least
* once before this method will be invoked.
*
* <p>If not all of the data needed to be written will fit in {@code buffer}, you can return
* {@code true} to indicate that you would like to write more data.
*
* <p>Otherwise, return {@code false} if you have no more data to write to stdin. (You can always
* invoke {@link ListeningProcessExecutor.LaunchedProcess#wantWrite()} any time in the future.
*/
@SuppressWarnings("unused") // Unused parameters are meant for subclasses to override and use.
protected boolean onStdinCharsReady(CharBuffer buffer) {
return false;
}
/**
* Called when there is an error encoding string data received from {@link
* #onStdinCharsReady(CharBuffer)}.
*
* @param result The {@link CoderResult} indicating encoder error
*/
protected void onStdinEncoderError(CoderResult result) {
throw new RuntimeException(result.toString());
}
/**
* Override this to receive decoded Unicode Java string data read from stdout.
*
* <p>Make sure to set the {@link CharBuffer#position() position} of {@code buffer} to indicate
* how much data you have read before returning.
*
* @param buffer The {@link CharBuffer} receiving Unicode string data.
*/
@SuppressWarnings("unused") // Unused parameters are meant for subclasses to override and use.
protected void onStdoutChars(CharBuffer buffer, boolean closed, CoderResult coderResult) {
// Consume the entire buffer by default.
buffer.position(buffer.limit());
}
/**
* Override this to receive decoded Unicode Java string data read from stderr.
*
* <p>Make sure to set the {@link CharBuffer#position() position} of {@code buffer} to indicate
* how much data you have read before returning.
*
* @param buffer The {@link CharBuffer} receiving Unicode string data.
*/
@SuppressWarnings("unused") // Unused parameters are meant for subclasses to override and use.
protected void onStderrChars(CharBuffer buffer, boolean closed, CoderResult coderResult) {
// Consume the entire buffer by default.
buffer.position(buffer.limit());
}
}