001/* 
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 *
017 */
018
019package org.apache.commons.exec;
020
021import java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.OutputStream;
024
025/**
026 * Base class to connect a logging system to the output and/or
027 * error stream of then external process. The implementation
028 * parses the incoming data to construct a line and passes
029 * the complete line to an user-defined implementation.
030 *
031 * @version $Id: LogOutputStream.java 1564598 2014-02-05 01:13:39Z ggregory $
032 */
033public abstract class LogOutputStream
034  extends OutputStream {
035
036    /** Initial buffer size. */
037    private static final int INTIAL_SIZE = 132;
038
039    /** Carriage return */
040    private static final int CR = 0x0d;
041
042    /** Linefeed */
043    private static final int LF = 0x0a;
044
045    /** the internal buffer */
046    private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(
047      INTIAL_SIZE);
048
049    private boolean skip = false;
050
051    private final int level;
052
053    /**
054     * Creates a new instance of this class.
055     * Uses the default level of 999.
056     */
057    public LogOutputStream() {
058        this(999);
059    }
060
061    /**
062     * Creates a new instance of this class.
063     *
064     * @param level loglevel used to log data written to this stream.
065     */
066    public LogOutputStream(final int level) {
067        this.level = level;
068    }
069
070    /**
071     * Write the data to the buffer and flush the buffer, if a line separator is
072     * detected.
073     *
074     * @param cc data to log (byte).
075     * @see java.io.OutputStream#write(int)
076     */
077    @Override
078    public void write(final int cc) throws IOException {
079        final byte c = (byte) cc;
080        if (c == '\n' || c == '\r') {
081            if (!skip) {
082                processBuffer();
083            }
084        } else {
085            buffer.write(cc);
086        }
087        skip = c == '\r';
088    }
089
090    /**
091     * Flush this log stream.
092     *
093     * @see java.io.OutputStream#flush()
094     */
095    @Override
096    public void flush() {
097        if (buffer.size() > 0) {
098            processBuffer();
099        }
100    }
101
102    /**
103     * Writes all remaining data from the buffer.
104     *
105     * @see java.io.OutputStream#close()
106     */
107    @Override
108    public void close() throws IOException {
109        if (buffer.size() > 0) {
110            processBuffer();
111        }
112        super.close();
113    }
114
115    /**
116     * @return the trace level of the log system
117     */
118    public int getMessageLevel() {
119        return level;
120    }
121
122    /**
123     * Write a block of characters to the output stream
124     *
125     * @param b the array containing the data
126     * @param off the offset into the array where data starts
127     * @param len the length of block
128     * @throws java.io.IOException if the data cannot be written into the stream.
129     * @see java.io.OutputStream#write(byte[], int, int)
130     */
131    @Override
132    public void write(final byte[] b, final int off, final int len)
133            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    /**
158     * Converts the buffer to a string and sends it to <code>processLine</code>.
159     */
160    protected void processBuffer() {
161        processLine(buffer.toString());
162        buffer.reset();
163    }
164
165    /**
166     * Logs a line to the log system of the user.
167     *
168     * @param line
169     *            the line to log.
170     */
171    protected void processLine(final String line) {
172        processLine(line, level);
173    }
174
175    /**
176     * Logs a line to the log system of the user.
177     *
178     * @param line the line to log.
179     * @param logLevel the log level to use
180     */
181    protected abstract void processLine(final String line, final int logLevel);
182}