/* * This file is part of ELKI: * Environment for Developing KDD-Applications Supported by Index-Structures * * Copyright (C) 2017 * ELKI Development Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.lmu.ifi.dbs.elki.logging; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; /** * Class to write to Output Streams, IGNORING {@link #close()}, with a special * newline handling and always flushing. * * This is meant to wrap logging output to the console. * * @author Erich Schubert * @since 0.2 */ public class OutputStreamLogger extends OutputStreamWriter { /** * Flag to signal if we have had a newline recently. */ private int charsSinceNewline = 0; /** * Carriage return character. */ protected static final char CARRIAGE_RETURN = '\r'; /** * Unix newline */ public static final char UNIX_NEWLINE = '\n'; /** * Newline string. */ public static final String NEWLINE = System.getProperty("line.separator"); /** * Newline as char array. */ public static final char[] NEWLINEC = NEWLINE.toCharArray(); /** * Whitespace. */ public static final String WHITESPACE = " "; /** * Constructor. * * @param out Output stream */ public OutputStreamLogger(OutputStream out) { super(out); } /** * Constructor. * * @param out Output stream * @param charsetName Character set name * @throws UnsupportedEncodingException thrown on unknown character sets */ public OutputStreamLogger(OutputStream out, String charsetName) throws UnsupportedEncodingException { super(out, charsetName); } /** * Constructor. * * @param out Output Stream * @param cs Character set to use */ public OutputStreamLogger(OutputStream out, Charset cs) { super(out, cs); } /** * Constructor. * * @param out Output Stream * @param enc Charset encoder */ public OutputStreamLogger(OutputStream out, CharsetEncoder enc) { super(out, enc); } /** * Close command - will be IGNORED. */ @Override public void close() { // IGNORE any close command. } /** * Count the tailing non-newline characters. * * @param cbuf Character buffer * @param off Offset * @param len Range * @return number of tailing non-newline character */ private int tailingNonNewline(char[] cbuf, int off, int len) { for(int cnt = 0; cnt < len; cnt++) { final int pos = off + (len - 1) - cnt; if(cbuf[pos] == UNIX_NEWLINE) { return cnt; } if(cbuf[pos] == CARRIAGE_RETURN) { return cnt; } // TODO: need to compare to NEWLINEC, too? } return len; } /** * Count the tailing non-newline characters. * * @param str String * @param off Offset * @param len Range * @return number of tailing non-newline character */ private int tailingNonNewline(String str, int off, int len) { for(int cnt = 0; cnt < len; cnt++) { final int pos = off + (len - 1) - cnt; if(str.charAt(pos) == UNIX_NEWLINE) { return cnt; } if(str.charAt(pos) == CARRIAGE_RETURN) { return cnt; } // TODO: need to compare to NEWLINE, too? } return len; } /** * Count the number of non-newline characters before first newline in the string. * * @param cbuf character buffer * @param off offset * @param len length * @return number of non-newline characters */ private int countNonNewline(char[] cbuf, int off, int len) { for(int cnt = 0; cnt < len; cnt++) { final int pos = off + cnt; if(cbuf[pos] == UNIX_NEWLINE) { return cnt; } if(cbuf[pos] == CARRIAGE_RETURN) { return cnt; } } return len; } /** * Count the number of non-newline characters before first newline in the string. * * @param str String * @param off offset * @param len length * @return number of non-newline characters */ private int countNonNewline(String str, int off, int len) { for(int cnt = 0; cnt < len; cnt++) { final int pos = off + cnt; if(str.charAt(pos) == UNIX_NEWLINE) { return cnt; } if(str.charAt(pos) == CARRIAGE_RETURN) { return cnt; } } return len; } /** * Writer that keeps track of when it hasn't seen a newline yet, will * auto-insert newlines except when lines start with a carriage return. */ @Override public void write(char[] cbuf, int off, int len) throws IOException { if(len <= 0) { return; } // if we havn't last seen a newline, and don't get a CR, insert a newline. if(charsSinceNewline > 0) { if(cbuf[off] != CARRIAGE_RETURN) { super.write(NEWLINEC, 0, NEWLINEC.length); charsSinceNewline = 0; } else { // length of this line: int nonnl = countNonNewline(cbuf, off + 1, len - 1); // clear the existing chars. if(nonnl < charsSinceNewline) { super.write(CARRIAGE_RETURN); while(charsSinceNewline > 0) { final int n = Math.min(charsSinceNewline, WHITESPACE.length()); super.write(WHITESPACE, 0, n); charsSinceNewline -= n; } } else { charsSinceNewline = 0; } } } charsSinceNewline = tailingNonNewline(cbuf, off, len); super.write(cbuf, off, len); flush(); } /** * Writer that keeps track of when it hasn't seen a newline yet, will * auto-insert newlines except when lines start with a carriage return. */ @Override public void write(String str, int off, int len) throws IOException { if(len <= 0) { return; } // if we havn't last seen a newline, and don't get a CR, insert a newline. if(charsSinceNewline > 0) { if(str.charAt(off) != CARRIAGE_RETURN) { super.write(NEWLINEC, 0, NEWLINEC.length); charsSinceNewline = 0; } else { // length of this line: int nonnl = countNonNewline(str, off + 1, len - 1); // clear the existing chars. if(nonnl < charsSinceNewline) { super.write(CARRIAGE_RETURN); while(charsSinceNewline > 0) { final int n = Math.min(charsSinceNewline, WHITESPACE.length()); super.write(WHITESPACE, 0, n); charsSinceNewline -= n; } } else { charsSinceNewline = 0; } } } charsSinceNewline = tailingNonNewline(str, off, len); super.write(str, off, len); flush(); } }