View Javadoc
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    *      http://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 }