001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.commons.exec; 021 022import java.io.ByteArrayOutputStream; 023import java.io.IOException; 024import java.io.OutputStream; 025import java.nio.charset.Charset; 026 027/** 028 * Base class to connect a logging system to the output and/or error stream of then external process. The implementation parses the incoming data to construct a 029 * line and passes the complete line to a user-defined implementation. 030 */ 031public abstract class LogOutputStream extends OutputStream { 032 033 private static final class ByteArrayOutputStreamX extends ByteArrayOutputStream { 034 private ByteArrayOutputStreamX(final int size) { 035 super(size); 036 } 037 038 public synchronized String toString(final Charset charset) { 039 return new String(buf, 0, count, charset); 040 } 041 } 042 043 /** Initial buffer size. */ 044 private static final int INITIAL_SIZE = 132; 045 046 /** Carriage return. */ 047 private static final int CR = 0x0d; 048 049 /** Line-feed. */ 050 private static final int LF = 0x0a; 051 052 /** The internal buffer. */ 053 private final ByteArrayOutputStreamX buffer = new ByteArrayOutputStreamX(INITIAL_SIZE); 054 055 /** 056 * Last written char was a CR. 057 */ 058 private boolean skip; 059 060 /** 061 * Level used to log data written to this stream. 062 */ 063 private final int level; 064 065 /** 066 * Character Set to use when processing lines. 067 */ 068 private final Charset charset; 069 070 /** 071 * Creates a new instance of this class. Uses the default level of 999. 072 */ 073 public LogOutputStream() { 074 this(999); 075 } 076 077 /** 078 * Creates a new instance of this class. 079 * 080 * @param level level used to log data written to this stream. 081 */ 082 public LogOutputStream(final int level) { 083 this(level, null); 084 } 085 086 /** 087 * Creates a new instance of this class, specifying the character set that should be used for outputting the string for each line 088 * 089 * @param level level used to log data written to this stream. 090 * @param charset Character Set to use when processing lines. 091 */ 092 public LogOutputStream(final int level, final Charset charset) { 093 this.level = level; 094 this.charset = charset == null ? Charset.defaultCharset() : charset; 095 } 096 097 /** 098 * Writes all remaining data from the buffer. 099 * 100 * @see OutputStream#close() 101 */ 102 @Override 103 public void close() throws IOException { 104 if (buffer.size() > 0) { 105 processBuffer(); 106 } 107 super.close(); 108 } 109 110 /** 111 * Flushes this log stream. 112 * 113 * @see OutputStream#flush() 114 */ 115 @Override 116 public void flush() { 117 if (buffer.size() > 0) { 118 processBuffer(); 119 } 120 } 121 122 /** 123 * Gets the trace level of the log system. 124 * 125 * @return the trace level of the log system. 126 */ 127 public int getMessageLevel() { 128 return level; 129 } 130 131 /** 132 * Converts the buffer to a string and sends it to {@code processLine}. 133 */ 134 protected void processBuffer() { 135 processLine(buffer.toString(charset)); 136 buffer.reset(); 137 } 138 139 /** 140 * Logs a line to the log system of the user. 141 * 142 * @param line the line to log. 143 */ 144 protected void processLine(final String line) { 145 processLine(line, level); 146 } 147 148 /** 149 * Logs a line to the log system of the user. 150 * 151 * @param line the line to log. 152 * @param logLevel the log level to use 153 */ 154 protected abstract void processLine(String line, int logLevel); 155 156 /** 157 * Writes a block of characters to the output stream. 158 * 159 * @param b the array containing the data. 160 * @param off the offset into the array where data starts. 161 * @param len the length of block. 162 * @throws IOException if the data cannot be written into the stream. 163 * @see OutputStream#write(byte[], int, int) 164 */ 165 @Override 166 public void write(final byte[] b, final int off, final int len) throws IOException { 167 // find the line breaks and pass other chars through in blocks 168 int offset = off; 169 int blockStartOffset = offset; 170 int remaining = len; 171 while (remaining > 0) { 172 while (remaining > 0 && b[offset] != LF && b[offset] != CR) { 173 offset++; 174 remaining--; 175 } 176 // either end of buffer or a line separator char 177 final int blockLength = offset - blockStartOffset; 178 if (blockLength > 0) { 179 buffer.write(b, blockStartOffset, blockLength); 180 } 181 while (remaining > 0 && (b[offset] == LF || b[offset] == CR)) { 182 write(b[offset]); 183 offset++; 184 remaining--; 185 } 186 blockStartOffset = offset; 187 } 188 } 189 190 /** 191 * Writes the data to the buffer and flush the buffer, if a line separator is detected. 192 * 193 * @param cc data to log (byte). 194 * @see OutputStream#write(int) 195 */ 196 @Override 197 public void write(final int cc) throws IOException { 198 final byte c = (byte) cc; 199 if (c == '\n' || c == '\r') { 200 if (!skip) { 201 processBuffer(); 202 } 203 } else { 204 buffer.write(cc); 205 } 206 skip = c == '\r'; 207 } 208}