-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimize system print streams when processing ANSI sequences
- Loading branch information
Showing
3 changed files
with
357 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
228 changes: 228 additions & 0 deletions
228
jansi/src/main/java/org/fusesource/jansi/AnsiNoSyncOutputStream.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
/* | ||
* Copyright (C) 2009-2020 the original author(s). | ||
* | ||
* 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.fusesource.jansi; | ||
|
||
import java.io.FilterOutputStream; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.util.ArrayList; | ||
|
||
/** | ||
* A ANSI print stream extracts ANSI escape codes written to | ||
* an output stream and calls corresponding <code>AnsiProcessor.process*</code> methods. | ||
* This particular class is not synchronized for improved performances. | ||
* | ||
* <p>For more information about ANSI escape codes, see | ||
* <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">Wikipedia article</a> | ||
* | ||
* @author Guillaume Nodet | ||
* @since 1.0 | ||
* @see AnsiProcessor | ||
*/ | ||
public class AnsiNoSyncOutputStream extends FilterOutputStream { | ||
|
||
public static final byte[] RESET_CODE = "\033[0m".getBytes(); | ||
|
||
private static final int LOOKING_FOR_FIRST_ESC_CHAR = 0; | ||
private static final int LOOKING_FOR_SECOND_ESC_CHAR = 1; | ||
private static final int LOOKING_FOR_NEXT_ARG = 2; | ||
private static final int LOOKING_FOR_STR_ARG_END = 3; | ||
private static final int LOOKING_FOR_INT_ARG_END = 4; | ||
private static final int LOOKING_FOR_OSC_COMMAND = 5; | ||
private static final int LOOKING_FOR_OSC_COMMAND_END = 6; | ||
private static final int LOOKING_FOR_OSC_PARAM = 7; | ||
private static final int LOOKING_FOR_ST = 8; | ||
private static final int LOOKING_FOR_CHARSET = 9; | ||
|
||
private static final int FIRST_ESC_CHAR = 27; | ||
private static final int SECOND_ESC_CHAR = '['; | ||
private static final int SECOND_OSC_CHAR = ']'; | ||
private static final int BEL = 7; | ||
private static final int SECOND_ST_CHAR = '\\'; | ||
private static final int SECOND_CHARSET0_CHAR = '('; | ||
private static final int SECOND_CHARSET1_CHAR = ')'; | ||
|
||
private final AnsiProcessor ap; | ||
private final static int MAX_ESCAPE_SEQUENCE_LENGTH = 100; | ||
private final byte[] buffer = new byte[MAX_ESCAPE_SEQUENCE_LENGTH]; | ||
private int pos = 0; | ||
private int startOfValue; | ||
private final ArrayList<Object> options = new ArrayList<Object>(); | ||
private int state = LOOKING_FOR_FIRST_ESC_CHAR; | ||
|
||
public AnsiNoSyncOutputStream(OutputStream os, AnsiProcessor ap) { | ||
super(os); | ||
this.ap = ap; | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
@Override | ||
public void write(int data) throws IOException { | ||
switch (state) { | ||
case LOOKING_FOR_FIRST_ESC_CHAR: | ||
if (data == FIRST_ESC_CHAR) { | ||
buffer[pos++] = (byte) data; | ||
state = LOOKING_FOR_SECOND_ESC_CHAR; | ||
} else { | ||
out.write(data); | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_SECOND_ESC_CHAR: | ||
buffer[pos++] = (byte) data; | ||
if (data == SECOND_ESC_CHAR) { | ||
state = LOOKING_FOR_NEXT_ARG; | ||
} else if (data == SECOND_OSC_CHAR) { | ||
state = LOOKING_FOR_OSC_COMMAND; | ||
} else if (data == SECOND_CHARSET0_CHAR) { | ||
options.add(0); | ||
state = LOOKING_FOR_CHARSET; | ||
} else if (data == SECOND_CHARSET1_CHAR) { | ||
options.add(1); | ||
state = LOOKING_FOR_CHARSET; | ||
} else { | ||
reset(false); | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_NEXT_ARG: | ||
buffer[pos++] = (byte) data; | ||
if ('"' == data) { | ||
startOfValue = pos - 1; | ||
state = LOOKING_FOR_STR_ARG_END; | ||
} else if ('0' <= data && data <= '9') { | ||
startOfValue = pos - 1; | ||
state = LOOKING_FOR_INT_ARG_END; | ||
} else if (';' == data) { | ||
options.add(null); | ||
} else if ('?' == data) { | ||
options.add('?'); | ||
} else if ('=' == data) { | ||
options.add('='); | ||
} else { | ||
reset(ap.processEscapeCommand(options, data)); | ||
} | ||
break; | ||
default: | ||
break; | ||
|
||
case LOOKING_FOR_INT_ARG_END: | ||
buffer[pos++] = (byte) data; | ||
if (!('0' <= data && data <= '9')) { | ||
String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue); | ||
Integer value = Integer.valueOf(strValue); | ||
options.add(value); | ||
if (data == ';') { | ||
state = LOOKING_FOR_NEXT_ARG; | ||
} else { | ||
reset(ap.processEscapeCommand(options, data)); | ||
} | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_STR_ARG_END: | ||
buffer[pos++] = (byte) data; | ||
if ('"' != data) { | ||
String value = new String(buffer, startOfValue, (pos - 1) - startOfValue); | ||
options.add(value); | ||
if (data == ';') { | ||
state = LOOKING_FOR_NEXT_ARG; | ||
} else { | ||
reset(ap.processEscapeCommand(options, data)); | ||
} | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_OSC_COMMAND: | ||
buffer[pos++] = (byte) data; | ||
if ('0' <= data && data <= '9') { | ||
startOfValue = pos - 1; | ||
state = LOOKING_FOR_OSC_COMMAND_END; | ||
} else { | ||
reset(false); | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_OSC_COMMAND_END: | ||
buffer[pos++] = (byte) data; | ||
if (';' == data) { | ||
String strValue = new String(buffer, startOfValue, (pos - 1) - startOfValue); | ||
Integer value = Integer.valueOf(strValue); | ||
options.add(value); | ||
startOfValue = pos; | ||
state = LOOKING_FOR_OSC_PARAM; | ||
} else if ('0' <= data && data <= '9') { | ||
// already pushed digit to buffer, just keep looking | ||
} else { | ||
// oops, did not expect this | ||
reset(false); | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_OSC_PARAM: | ||
buffer[pos++] = (byte) data; | ||
if (BEL == data) { | ||
String value = new String(buffer, startOfValue, (pos - 1) - startOfValue); | ||
options.add(value); | ||
reset(ap.processOperatingSystemCommand(options)); | ||
} else if (FIRST_ESC_CHAR == data) { | ||
state = LOOKING_FOR_ST; | ||
} else { | ||
// just keep looking while adding text | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_ST: | ||
buffer[pos++] = (byte) data; | ||
if (SECOND_ST_CHAR == data) { | ||
String value = new String(buffer, startOfValue, (pos - 2) - startOfValue); | ||
options.add(value); | ||
reset(ap.processOperatingSystemCommand(options)); | ||
} else { | ||
state = LOOKING_FOR_OSC_PARAM; | ||
} | ||
break; | ||
|
||
case LOOKING_FOR_CHARSET: | ||
options.add((char) data); | ||
reset(ap.processCharsetSelect(options)); | ||
break; | ||
} | ||
|
||
// Is it just too long? | ||
if (pos >= buffer.length) { | ||
reset(false); | ||
} | ||
} | ||
|
||
/** | ||
* Resets all state to continue with regular parsing | ||
* @param skipBuffer if current buffer should be skipped or written to out | ||
* @throws IOException | ||
*/ | ||
private void reset(boolean skipBuffer) throws IOException { | ||
if (!skipBuffer) { | ||
out.write(buffer, 0, pos); | ||
} | ||
pos = 0; | ||
startOfValue = 0; | ||
options.clear(); | ||
state = LOOKING_FOR_FIRST_ESC_CHAR; | ||
} | ||
|
||
} |
69 changes: 69 additions & 0 deletions
69
jansi/src/main/java/org/fusesource/jansi/BufferedNoSyncOutputStream.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* Copyright (C) 2009-2020 the original author(s). | ||
* | ||
* 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.fusesource.jansi; | ||
|
||
import java.io.FilterOutputStream; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
|
||
/** | ||
* A simple buffering output stream with no synchronization. | ||
*/ | ||
public class BufferedNoSyncOutputStream extends FilterOutputStream { | ||
|
||
protected final byte buf[] = new byte[8192]; | ||
protected int count; | ||
|
||
public BufferedNoSyncOutputStream(OutputStream out) { | ||
super(out); | ||
} | ||
|
||
@Override | ||
public void write(int b) throws IOException { | ||
if (count >= buf.length) { | ||
flushBuffer(); | ||
} | ||
buf[count++] = (byte) b; | ||
} | ||
|
||
@Override | ||
public void write(byte b[], int off, int len) throws IOException { | ||
if (len >= buf.length) { | ||
flushBuffer(); | ||
out.write(b, off, len); | ||
return; | ||
} | ||
if (len > buf.length - count) { | ||
flushBuffer(); | ||
} | ||
System.arraycopy(b, off, buf, count, len); | ||
count += len; | ||
} | ||
|
||
private void flushBuffer() throws IOException { | ||
if (count > 0) { | ||
out.write(buf, 0, count); | ||
count = 0; | ||
} | ||
} | ||
|
||
@Override | ||
public void flush() throws IOException { | ||
flushBuffer(); | ||
out.flush(); | ||
} | ||
|
||
} |