LogOutputStream.java

  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. package org.apache.commons.exec;

  18. import java.io.ByteArrayOutputStream;
  19. import java.io.IOException;
  20. import java.io.OutputStream;
  21. import java.nio.charset.Charset;

  22. /**
  23.  * 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
  24.  * line and passes the complete line to an user-defined implementation.
  25.  */
  26. public abstract class LogOutputStream extends OutputStream {

  27.     private static final class ByteArrayOutputStreamX extends ByteArrayOutputStream {
  28.         private ByteArrayOutputStreamX(final int size) {
  29.             super(size);
  30.         }

  31.         public synchronized String toString(final Charset charset) {
  32.             return new String(buf, 0, count, charset);
  33.         }
  34.     }

  35.     /** Initial buffer size. */
  36.     private static final int INTIAL_SIZE = 132;

  37.     /** Carriage return. */
  38.     private static final int CR = 0x0d;

  39.     /** Line-feed. */
  40.     private static final int LF = 0x0a;

  41.     /** The internal buffer. */
  42.     private final ByteArrayOutputStreamX buffer = new ByteArrayOutputStreamX(INTIAL_SIZE);

  43.     private boolean skip;

  44.     private final int level;

  45.     private final Charset charset;

  46.     /**
  47.      * Creates a new instance of this class. Uses the default level of 999.
  48.      */
  49.     public LogOutputStream() {
  50.         this(999);
  51.     }

  52.     /**
  53.      * Creates a new instance of this class.
  54.      *
  55.      * @param level level used to log data written to this stream.
  56.      */
  57.     public LogOutputStream(final int level) {
  58.         this(level, null);
  59.     }

  60.     /**
  61.      * Creates a new instance of this class, specifying the character set that should be used for outputting the string for each line
  62.      *
  63.      * @param level   level used to log data written to this stream.
  64.      * @param charset Character Set to use when processing lines.
  65.      */
  66.     public LogOutputStream(final int level, final Charset charset) {
  67.         this.level = level;
  68.         this.charset = charset == null ? Charset.defaultCharset() : charset;
  69.     }

  70.     /**
  71.      * Writes all remaining data from the buffer.
  72.      *
  73.      * @see OutputStream#close()
  74.      */
  75.     @Override
  76.     public void close() throws IOException {
  77.         if (buffer.size() > 0) {
  78.             processBuffer();
  79.         }
  80.         super.close();
  81.     }

  82.     /**
  83.      * Flushes this log stream.
  84.      *
  85.      * @see OutputStream#flush()
  86.      */
  87.     @Override
  88.     public void flush() {
  89.         if (buffer.size() > 0) {
  90.             processBuffer();
  91.         }
  92.     }

  93.     /**
  94.      * Gets the trace level of the log system.
  95.      *
  96.      * @return the trace level of the log system.
  97.      */
  98.     public int getMessageLevel() {
  99.         return level;
  100.     }

  101.     /**
  102.      * Converts the buffer to a string and sends it to {@code processLine}.
  103.      */
  104.     protected void processBuffer() {
  105.         processLine(buffer.toString(charset));
  106.         buffer.reset();
  107.     }

  108.     /**
  109.      * Logs a line to the log system of the user.
  110.      *
  111.      * @param line the line to log.
  112.      */
  113.     protected void processLine(final String line) {
  114.         processLine(line, level);
  115.     }

  116.     /**
  117.      * Logs a line to the log system of the user.
  118.      *
  119.      * @param line     the line to log.
  120.      * @param logLevel the log level to use
  121.      */
  122.     protected abstract void processLine(final String line, final int logLevel);

  123.     /**
  124.      * Writes a block of characters to the output stream.
  125.      *
  126.      * @param b   the array containing the data.
  127.      * @param off the offset into the array where data starts.
  128.      * @param len the length of block.
  129.      * @throws IOException if the data cannot be written into the stream.
  130.      * @see OutputStream#write(byte[], int, int)
  131.      */
  132.     @Override
  133.     public void write(final byte[] b, final int off, final int len) throws IOException {
  134.         // find the line breaks and pass other chars through in blocks
  135.         int offset = off;
  136.         int blockStartOffset = offset;
  137.         int remaining = len;
  138.         while (remaining > 0) {
  139.             while (remaining > 0 && b[offset] != LF && b[offset] != CR) {
  140.                 offset++;
  141.                 remaining--;
  142.             }
  143.             // either end of buffer or a line separator char
  144.             final int blockLength = offset - blockStartOffset;
  145.             if (blockLength > 0) {
  146.                 buffer.write(b, blockStartOffset, blockLength);
  147.             }
  148.             while (remaining > 0 && (b[offset] == LF || b[offset] == CR)) {
  149.                 write(b[offset]);
  150.                 offset++;
  151.                 remaining--;
  152.             }
  153.             blockStartOffset = offset;
  154.         }
  155.     }

  156.     /**
  157.      * Writes the data to the buffer and flush the buffer, if a line separator is detected.
  158.      *
  159.      * @param cc data to log (byte).
  160.      * @see OutputStream#write(int)
  161.      */
  162.     @Override
  163.     public void write(final int cc) throws IOException {
  164.         final byte c = (byte) cc;
  165.         if (c == '\n' || c == '\r') {
  166.             if (!skip) {
  167.                 processBuffer();
  168.             }
  169.         } else {
  170.             buffer.write(cc);
  171.         }
  172.         skip = c == '\r';
  173.     }
  174. }