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:
019: package org.apache.tools.ant;
020:
021: import java.io.ByteArrayOutputStream;
022: import java.io.IOException;
023: import java.io.OutputStream;
024: import java.util.WeakHashMap;
025:
026: /**
027: * Logs content written by a thread and forwards the buffers onto the
028: * project object which will forward the content to the appropriate
029: * task.
030: *
031: * @since 1.4
032: */
033: public class DemuxOutputStream extends OutputStream {
034:
035: /**
036: * A data class to store information about a buffer. Such information
037: * is stored on a per-thread basis.
038: */
039: private static class BufferInfo {
040: /**
041: * The per-thread output stream.
042: */
043: private ByteArrayOutputStream buffer;
044:
045: /**
046: * Indicates we have just seen a carriage return. It may be part of
047: * a crlf pair or a single cr invoking processBuffer twice.
048: */
049: private boolean crSeen = false;
050: }
051:
052: /** Maximum buffer size. */
053: private static final int MAX_SIZE = 1024;
054:
055: /** Initial buffer size. */
056: private static final int INTIAL_SIZE = 132;
057:
058: /** Carriage return */
059: private static final int CR = 0x0d;
060:
061: /** Linefeed */
062: private static final int LF = 0x0a;
063:
064: /** Mapping from thread to buffer (Thread to BufferInfo). */
065: private WeakHashMap buffers = new WeakHashMap();
066:
067: /**
068: * The project to send output to.
069: */
070: private Project project;
071:
072: /**
073: * Whether or not this stream represents an error stream.
074: */
075: private boolean isErrorStream;
076:
077: /**
078: * Creates a new instance of this class.
079: *
080: * @param project The project instance for which output is being
081: * demultiplexed. Must not be <code>null</code>.
082: * @param isErrorStream <code>true</code> if this is the error string,
083: * otherwise a normal output stream. This is
084: * passed to the project so it knows
085: * which stream it is receiving.
086: */
087: public DemuxOutputStream(Project project, boolean isErrorStream) {
088: this .project = project;
089: this .isErrorStream = isErrorStream;
090: }
091:
092: /**
093: * Returns the buffer associated with the current thread.
094: *
095: * @return a BufferInfo for the current thread to write data to
096: */
097: private BufferInfo getBufferInfo() {
098: Thread current = Thread.currentThread();
099: BufferInfo bufferInfo = (BufferInfo) buffers.get(current);
100: if (bufferInfo == null) {
101: bufferInfo = new BufferInfo();
102: bufferInfo.buffer = new ByteArrayOutputStream(INTIAL_SIZE);
103: bufferInfo.crSeen = false;
104: buffers.put(current, bufferInfo);
105: }
106: return bufferInfo;
107: }
108:
109: /**
110: * Resets the buffer for the current thread.
111: */
112: private void resetBufferInfo() {
113: Thread current = Thread.currentThread();
114: BufferInfo bufferInfo = (BufferInfo) buffers.get(current);
115: try {
116: bufferInfo.buffer.close();
117: } catch (IOException e) {
118: // Shouldn't happen
119: }
120: bufferInfo.buffer = new ByteArrayOutputStream();
121: bufferInfo.crSeen = false;
122: }
123:
124: /**
125: * Removes the buffer for the current thread.
126: */
127: private void removeBuffer() {
128: Thread current = Thread.currentThread();
129: buffers.remove(current);
130: }
131:
132: /**
133: * Writes the data to the buffer and flushes the buffer if a line
134: * separator is detected or if the buffer has reached its maximum size.
135: *
136: * @param cc data to log (byte).
137: * @exception IOException if the data cannot be written to the stream
138: */
139: public void write(int cc) throws IOException {
140: final byte c = (byte) cc;
141:
142: BufferInfo bufferInfo = getBufferInfo();
143:
144: if (c == '\n') {
145: // LF is always end of line (i.e. CRLF or single LF)
146: bufferInfo.buffer.write(cc);
147: processBuffer(bufferInfo.buffer);
148: } else {
149: if (bufferInfo.crSeen) {
150: // CR without LF - send buffer then add char
151: processBuffer(bufferInfo.buffer);
152: }
153: // add into buffer
154: bufferInfo.buffer.write(cc);
155: }
156: bufferInfo.crSeen = (c == '\r');
157: if (!bufferInfo.crSeen && bufferInfo.buffer.size() > MAX_SIZE) {
158: processBuffer(bufferInfo.buffer);
159: }
160: }
161:
162: /**
163: * Converts the buffer to a string and sends it to the project.
164: *
165: * @param buffer the ByteArrayOutputStream used to collect the output
166: * until a line separator is seen.
167: *
168: * @see Project#demuxOutput(String,boolean)
169: */
170: protected void processBuffer(ByteArrayOutputStream buffer) {
171: String output = buffer.toString();
172: project.demuxOutput(output, isErrorStream);
173: resetBufferInfo();
174: }
175:
176: /**
177: * Converts the buffer to a string and sends it to the project.
178: *
179: * @param buffer the ByteArrayOutputStream used to collect the output
180: * until a line separator is seen.
181: *
182: * @see Project#demuxOutput(String,boolean)
183: */
184: protected void processFlush(ByteArrayOutputStream buffer) {
185: String output = buffer.toString();
186: project.demuxFlush(output, isErrorStream);
187: resetBufferInfo();
188: }
189:
190: /**
191: * Equivalent to flushing the stream.
192: *
193: * @exception IOException if there is a problem closing the stream.
194: *
195: * @see #flush
196: */
197: public void close() throws IOException {
198: flush();
199: removeBuffer();
200: }
201:
202: /**
203: * Writes all remaining data in the buffer associated
204: * with the current thread to the project.
205: *
206: * @exception IOException if there is a problem flushing the stream.
207: */
208: public void flush() throws IOException {
209: BufferInfo bufferInfo = getBufferInfo();
210: if (bufferInfo.buffer.size() > 0) {
211: processFlush(bufferInfo.buffer);
212: }
213: }
214:
215: /**
216: * Write a block of characters to the output stream
217: *
218: * @param b the array containing the data
219: * @param off the offset into the array where data starts
220: * @param len the length of block
221: *
222: * @throws IOException if the data cannot be written into the stream.
223: */
224: public void write(byte[] b, int off, int len) throws IOException {
225: // find the line breaks and pass other chars through in blocks
226: int offset = off;
227: int blockStartOffset = offset;
228: int remaining = len;
229: BufferInfo bufferInfo = getBufferInfo();
230: while (remaining > 0) {
231: while (remaining > 0 && b[offset] != LF && b[offset] != CR) {
232: offset++;
233: remaining--;
234: }
235: // either end of buffer or a line separator char
236: int blockLength = offset - blockStartOffset;
237: if (blockLength > 0) {
238: bufferInfo.buffer.write(b, blockStartOffset,
239: blockLength);
240: }
241: while (remaining > 0
242: && (b[offset] == LF || b[offset] == CR)) {
243: write(b[offset]);
244: offset++;
245: remaining--;
246: }
247: blockStartOffset = offset;
248: }
249: }
250: }
|