/* * 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 com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CoderResult; import java.nio.charset.StandardCharsets; import java.util.Iterator; import javax.annotation.Nullable; /** * Implementation of {@link ListeningProcessExecutor.ProcessListener} which decodes bytes to and * from Java String data and stores the result in memory. */ public class SimpleProcessListener extends AbstractCharsetProcessListener { private @Nullable ListeningProcessExecutor.LaunchedProcess process; private @Nullable Iterator<CharBuffer> nextStdInToWrite; private @Nullable CharBuffer stdInToWrite; private final StringBuilder stdout; private final StringBuilder stderr; /** * Constructs a {@link SimpleProcessListener} which closes stdin immediately and stores UTF-8 data * received on stdout and stderr in memory. */ public SimpleProcessListener() { this((Iterator<CharBuffer>) null, StandardCharsets.UTF_8); } /** * Constructs a {@link SimpleProcessListener} which writes {@code nextStdInToWrite} to stdin * encoded in UTF-8, closes it, and stores UTF-8 data received on stdout and stderr in memory. */ public SimpleProcessListener(CharSequence stdinToWrite) { this(stdinToWrite, StandardCharsets.UTF_8); } /** * Constructs a {@link SimpleProcessListener} which writes {@code nextStdInToWrite} to stdin * encoded using {@code charset}, closes it, and stores data decoded using {@code charset} * received on stdout and stderr in memory. */ public SimpleProcessListener(CharSequence stdinToWrite, Charset charset) { this(ImmutableList.of(CharBuffer.wrap(stdinToWrite)).iterator(), charset); } /** * Constructs a {@link SimpleProcessListener} which writes data from {@code nextStdInToWrite} to * stdin encoded using {@code charset}, closes it, and stores data decoded using {@code charset} * received on stdout and stderr in memory. */ public SimpleProcessListener(@Nullable Iterator<CharBuffer> stdinToWrite, Charset charset) { super(charset); this.process = null; this.nextStdInToWrite = stdinToWrite; this.stdout = new StringBuilder(); this.stderr = new StringBuilder(); } /** * Gets the entire contents of stdout sent by the process. Only call this after the process has * exited. */ public String getStdout() { Preconditions.checkState(process != null, "Process didn't start yet"); Preconditions.checkState(!process.isRunning(), "Process must not still be running"); return stdout.toString(); } /** * Gets the entire contents of stderr sent by the process. Only call this after the process has * exited. */ public String getStderr() { Preconditions.checkState(process != null, "Process didn't start yet"); Preconditions.checkState(!process.isRunning(), "Process must not still be running"); return stderr.toString(); } @Override public void onStart(ListeningProcessExecutor.LaunchedProcess process) { this.process = process; if (nextStdInToWrite == null) { this.process.closeStdin(/* force */ true); } } protected static boolean pushBytes(CharBuffer from, CharBuffer to) { int bytesAvailable = to.remaining(); if (from.remaining() <= bytesAvailable) { // This updates the position of both 'buffer' and 'nextStdInToWrite'. to.put(from); return false; } int oldLimit = from.limit(); from.limit(from.position() + bytesAvailable); // Same as above wrt updating position. to.put(from); from.limit(oldLimit); return true; } @Override protected boolean onStdinCharsReady(CharBuffer buffer) { if (nextStdInToWrite == null) { buffer.flip(); return false; } if (stdInToWrite == null && nextStdInToWrite.hasNext()) { stdInToWrite = nextStdInToWrite.next(); } if (stdInToWrite != null) { boolean hasMoreToWrite = pushBytes(stdInToWrite, buffer); if (!hasMoreToWrite) { stdInToWrite = nextStdInToWrite.hasNext() ? nextStdInToWrite.next() : null; } } if (stdInToWrite == null) { Preconditions.checkNotNull(process, "Process didn't start yet").closeStdin(/* force */ false); nextStdInToWrite = null; } buffer.flip(); return stdInToWrite != null; } @Override protected void onStdoutChars(CharBuffer buffer, boolean closed, CoderResult coderResult) { stdout.append(buffer); // Consume the entire buffer. buffer.position(buffer.limit()); } @Override protected void onStderrChars(CharBuffer buffer, boolean closed, CoderResult coderResult) { stderr.append(buffer); // Consume the entire buffer. buffer.position(buffer.limit()); } }