aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/util/AnsiStrippingOutputStream.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/util/AnsiStrippingOutputStream.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/util/AnsiStrippingOutputStream.java176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/util/AnsiStrippingOutputStream.java b/src/main/java/com/google/devtools/build/lib/util/AnsiStrippingOutputStream.java
new file mode 100644
index 0000000000..6c6b878543
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/util/AnsiStrippingOutputStream.java
@@ -0,0 +1,176 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// 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.google.devtools.build.lib.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A pass-thru {@link OutputStream} that strips ANSI control codes.
+ */
+public class AnsiStrippingOutputStream extends OutputStream {
+ // The idea is straightforward: the regexp for ANSI control codes is
+ // \x1b\[[;0-9]*[a-zA-Z] . Implementing it as a stream is a little ugly,
+ // though.
+
+ private enum State {
+ NORMAL,
+ AFTER_ESCAPE,
+ PARAMETER,
+ }
+
+ private byte[] outputBuffer;
+ private int outputBufferPos;
+
+ private static final int ESCAPE_BUFFER_LENGTH = 128;
+ private byte[] escapeCodeBuffer;
+ private int escapeCodeBufferPos;
+ private OutputStream output;
+ private State state;
+
+ public AnsiStrippingOutputStream(OutputStream output) {
+ this.output = output;
+ escapeCodeBuffer = new byte[ESCAPE_BUFFER_LENGTH];
+ escapeCodeBufferPos = 0;
+ state = State.NORMAL;
+ }
+
+ @Override
+ public synchronized void write(int b) throws IOException {
+ // As per the contract of OutputStream.write(int)
+ byte[] array = { (byte) (b & 0xff) };
+ write(array, 0, 1);
+ }
+
+ @Override
+ public synchronized void write(byte b[], int off, int len) throws IOException {
+ int i = 0;
+ if (state == State.NORMAL) {
+
+ // Avoid outputBuffer allocation entirely if that's possible
+ while ((i < len) && (b[off + i] != 0x1b)) {
+ i++;
+ }
+ if (i == len) {
+ output.write(b, off, len);
+ return;
+ }
+ }
+
+ // In the worst case, the contents of the escape buffer and the contents
+ // of the input buffer are both copied to the output, so the length of the
+ // output buffer should be the sum of the length of both these buffers.
+ outputBuffer = new byte[len + ESCAPE_BUFFER_LENGTH];
+ System.arraycopy(b, off, outputBuffer, 0, i);
+ outputBufferPos = i;
+
+ for (; i < len; i++) {
+ processByte(b[off + i]);
+ }
+
+ try {
+ output.write(outputBuffer, 0, outputBufferPos);
+ } finally {
+ outputBuffer = null; // Make it possible to garbage collect the array
+ }
+ }
+
+ private void processByte(byte b) {
+ switch (state) {
+ case NORMAL:
+ if (escapeCodeBufferPos != 0) {
+ throw new IllegalStateException();
+ }
+ if (b == 0x1b) {
+ state = State.AFTER_ESCAPE;
+ addByteToEscapeBuffer(b);
+ } else {
+ dumpByte(b);
+ }
+ break;
+
+ case AFTER_ESCAPE:
+ if (b == '[') {
+ state = State.PARAMETER;
+ addByteToEscapeBuffer(b);
+ } else if (b == 0x1b) {
+ dumpEscapeBuffer();
+ state = State.AFTER_ESCAPE;
+ addByteToEscapeBuffer(b);
+ } else {
+ dumpEscapeBuffer();
+ dumpByte(b);
+ state = State.NORMAL;
+ }
+ break;
+
+ case PARAMETER:
+ if ((b >= '0' && b <= '9') || b == ';') {
+ // Parameter continues
+ addByteToEscapeBuffer(b);
+ } else if ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) {
+ // Found a control sequence, discard it and revert to normal state
+ discardEscapeBuffer();
+ state = State.NORMAL;
+ } else if (b == 0x1b) {
+ // Another escape sequence begins immediately after, and this is
+ // an illegal escape sequence
+ dumpEscapeBuffer();
+ state = State.AFTER_ESCAPE;
+ addByteToEscapeBuffer(b);
+ } else {
+ // Illegal control sequence, output it
+ dumpEscapeBuffer();
+ state = State.NORMAL;
+ }
+ break;
+ }
+ }
+
+ private void addByteToEscapeBuffer(byte b) {
+ escapeCodeBuffer[escapeCodeBufferPos++] = b;
+ if (escapeCodeBufferPos == ESCAPE_BUFFER_LENGTH) {
+ // Buffer full. Assume that no sane code emits an ANSI control code this
+ // long and revert to normal state.
+ dumpEscapeBuffer();
+ state = State.NORMAL;
+ }
+ }
+
+ private void discardEscapeBuffer() {
+ escapeCodeBufferPos = 0;
+ }
+
+ private void dumpByte(byte b) {
+ outputBuffer[outputBufferPos++] = b;
+ }
+
+ private void dumpEscapeBuffer() {
+ System.arraycopy(escapeCodeBuffer, 0,
+ outputBuffer, outputBufferPos, escapeCodeBufferPos);
+ outputBufferPos += escapeCodeBufferPos;
+ escapeCodeBufferPos = 0;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ output.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ output.close();
+ }
+}