1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.exec;
19
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.nio.charset.Charset;
24
25 /**
26 * 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
27 * line and passes the complete line to an user-defined implementation.
28 */
29 public abstract class LogOutputStream extends OutputStream {
30
31 private static final class ByteArrayOutputStreamX extends ByteArrayOutputStream {
32 private ByteArrayOutputStreamX(final int size) {
33 super(size);
34 }
35
36 public synchronized String toString(final Charset charset) {
37 return new String(buf, 0, count, charset);
38 }
39 }
40
41 /** Initial buffer size. */
42 private static final int INTIAL_SIZE = 132;
43
44 /** Carriage return. */
45 private static final int CR = 0x0d;
46
47 /** Line-feed. */
48 private static final int LF = 0x0a;
49
50 /** The internal buffer. */
51 private final ByteArrayOutputStreamX buffer = new ByteArrayOutputStreamX(INTIAL_SIZE);
52
53 private boolean skip;
54
55 private final int level;
56
57 private final Charset charset;
58
59 /**
60 * Creates a new instance of this class. Uses the default level of 999.
61 */
62 public LogOutputStream() {
63 this(999);
64 }
65
66 /**
67 * Creates a new instance of this class.
68 *
69 * @param level level used to log data written to this stream.
70 */
71 public LogOutputStream(final int level) {
72 this(level, null);
73 }
74
75 /**
76 * Creates a new instance of this class, specifying the character set that should be used for outputting the string for each line
77 *
78 * @param level level used to log data written to this stream.
79 * @param charset Character Set to use when processing lines.
80 */
81 public LogOutputStream(final int level, final Charset charset) {
82 this.level = level;
83 this.charset = charset == null ? Charset.defaultCharset() : charset;
84 }
85
86 /**
87 * Writes all remaining data from the buffer.
88 *
89 * @see OutputStream#close()
90 */
91 @Override
92 public void close() throws IOException {
93 if (buffer.size() > 0) {
94 processBuffer();
95 }
96 super.close();
97 }
98
99 /**
100 * Flushes this log stream.
101 *
102 * @see OutputStream#flush()
103 */
104 @Override
105 public void flush() {
106 if (buffer.size() > 0) {
107 processBuffer();
108 }
109 }
110
111 /**
112 * Gets the trace level of the log system.
113 *
114 * @return the trace level of the log system.
115 */
116 public int getMessageLevel() {
117 return level;
118 }
119
120 /**
121 * Converts the buffer to a string and sends it to {@code processLine}.
122 */
123 protected void processBuffer() {
124 processLine(buffer.toString(charset));
125 buffer.reset();
126 }
127
128 /**
129 * Logs a line to the log system of the user.
130 *
131 * @param line the line to log.
132 */
133 protected void processLine(final String line) {
134 processLine(line, level);
135 }
136
137 /**
138 * Logs a line to the log system of the user.
139 *
140 * @param line the line to log.
141 * @param logLevel the log level to use
142 */
143 protected abstract void processLine(final String line, final int logLevel);
144
145 /**
146 * Writes a block of characters to the output stream.
147 *
148 * @param b the array containing the data.
149 * @param off the offset into the array where data starts.
150 * @param len the length of block.
151 * @throws IOException if the data cannot be written into the stream.
152 * @see OutputStream#write(byte[], int, int)
153 */
154 @Override
155 public void write(final byte[] b, final int off, final int len) throws IOException {
156 // find the line breaks and pass other chars through in blocks
157 int offset = off;
158 int blockStartOffset = offset;
159 int remaining = len;
160 while (remaining > 0) {
161 while (remaining > 0 && b[offset] != LF && b[offset] != CR) {
162 offset++;
163 remaining--;
164 }
165 // either end of buffer or a line separator char
166 final int blockLength = offset - blockStartOffset;
167 if (blockLength > 0) {
168 buffer.write(b, blockStartOffset, blockLength);
169 }
170 while (remaining > 0 && (b[offset] == LF || b[offset] == CR)) {
171 write(b[offset]);
172 offset++;
173 remaining--;
174 }
175 blockStartOffset = offset;
176 }
177 }
178
179 /**
180 * Writes the data to the buffer and flush the buffer, if a line separator is detected.
181 *
182 * @param cc data to log (byte).
183 * @see OutputStream#write(int)
184 */
185 @Override
186 public void write(final int cc) throws IOException {
187 final byte c = (byte) cc;
188 if (c == '\n' || c == '\r') {
189 if (!skip) {
190 processBuffer();
191 }
192 } else {
193 buffer.write(cc);
194 }
195 skip = c == '\r';
196 }
197 }