/* * Copyright (c) 2012-2015 iWave Software LLC * All Rights Reserved */ package com.iwave.utility.ssh; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import org.apache.log4j.Logger; public class CharStreamConsumer { public static final String DEFAULT_CHARSET = "UTF-8"; /** Optional logger for logging. */ private Logger logger; /** The thread to run the consumer. */ private volatile Thread thread = new Thread("CharStreamConsumer") { public void run() { CharStreamConsumer.this.run(); } }; /** Buffer for the contents of the InputStream. */ private StringBuffer buffer = new StringBuffer(); /** The stream to consume. */ private Reader in; /** Whether to strip control characters. */ private boolean stripControlChars = true; /** * Creates a CharStreamConsumer for consuming the data from the character stream. * * @param stream the stream to consume. */ public CharStreamConsumer(InputStream stream) throws UnsupportedEncodingException { this(stream, DEFAULT_CHARSET); } /** * Creates a CharStreamConsumer for consuming the data from the character stream. * * @param stream the stream to consume. * @param charset the charset to use. */ public CharStreamConsumer(InputStream stream, String charset) throws UnsupportedEncodingException { this(new InputStreamReader(new BufferedInputStream(stream), charset)); } /** * Creates a CharStreamConsumer for consuming the data from the character stream. * * @param stream the character stream. */ public CharStreamConsumer(Reader stream) { in = new BufferedReader(stream); thread.start(); } public boolean isStripControlChars() { return stripControlChars; } public void setStripControlChars(boolean stripControlChars) { this.stripControlChars = stripControlChars; } public Logger getLogger() { return logger; } public void setLogger(Logger logger) { this.logger = logger; } public void createLogger() { logger = Logger.getLogger(getClass()); } /** * Stops the monitor. */ public void close() { Thread current = thread; thread = null; if (current != null) { current.interrupt(); try { current.join(); } catch (InterruptedException e) { // Ignore } } } /** * Consumes the character stream until it runs out or is interrupted. */ private void run() { try { Thread current = Thread.currentThread(); char[] buf = new char[1024]; for (int len = in.read(buf); len != -1; len = in.read(buf)) { append(buf, 0, len); // Terminate early if the consumer was stopped if (current != thread) { break; } } } catch (IOException e) { logger.error(e.getMessage(), e); } trace("Finished"); } protected void append(char[] buf, int start, int len) { trace("Received: %s", new String(buf, start, len)); if (!stripControlChars) { buffer.append(buf, start, len); return; } int end = Math.min(buf.length, start + len); for (int i = start; i < end; i++) { // Look for ANSI escape sequences int escapeLen = getAnsiEscapeLength(buf, i, end); if (escapeLen > 0) { trace("Skipping ANSI escape: %s", new String(buf, i, escapeLen)); i += escapeLen - 1; } else { char ch = buf[i]; if (!isControlChar(ch)) { buffer.append(ch); } else { trace("Stripped Control Char: %02X", (int) ch); } } } } /** * Gets the length of the ANSI escape sequence at the given position in the buffer. If the * current position is not an ANSI escape, this will return 0; * * @param buf the current buffer. * @param start the start position. * @param len the length available in the buffer. * @return the ANSI escape length. */ protected int getAnsiEscapeLength(char[] buf, int start, int len) { if (buf[start] != 0x1B) { return 0; } // This means there is part of an escape sequence... kind of a problem if (start + 1 >= len) { return 1; } // 2 Character escape sequence has anything but 0x5B ([) if (buf[start + 1] != 0x5B) { return 2; } // Look for a terminating character to the escape (0x40-0x7E) int escapeLen = 2; while ((start + escapeLen) < len) { char ch = buf[start + escapeLen]; escapeLen++; if ((ch >= 0x40) && (ch <= 0x7E)) { break; } } return escapeLen; } /** * Determines if the character is a control character. * * @param ch the character. * @return true if the character is a control character. */ protected boolean isControlChar(char ch) { return Character.isISOControl(ch) && !Character.isWhitespace(ch); } /** * Gets the raw buffer holding the output. * * @return the buffer. */ public StringBuffer getBuffer() { return buffer; } /** * Returns a String based on the contents of the buffer. */ public String toString() { return buffer.toString(); } /** * Outputs a trace level message, only if a logger is present and enabled for tracing. * * @param message the message. * @param args the message args. */ protected void trace(String message, Object... args) { if (logger != null && logger.isTraceEnabled()) { if (args.length > 0) { logger.trace(String.format(message, args)); } else { logger.trace(message); } } } }