001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.tools.server;
028:
029: import java.io.ByteArrayInputStream;
030: import java.io.EOFException;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.io.ObjectInputStream;
034: import java.io.ObjectOutputStream;
035: import java.io.OutputStream;
036: import java.io.Serializable;
037:
038: /**
039: * This utility-class is a buffer for capturing system-out and
040: * system-error output to a byte[], plus reading the captured
041: * output back with the interleaved write-ordering preserved.
042: * <p>
043: * Example usage:<pre>
044: * // create streams
045: * DualStreamBuffer dsb = new DualStreamBuffer();
046: * PrintStream pout = new PrintStream(dsb.getOutputStream(true));
047: * PrintStream perr = new PrintStream(dsb.getOutputStream(false));
048: * // use streams as usual
049: * pout.print("example std-out");
050: * perr.print("some error");
051: * pout.print("more std-out");
052: * // replay to standard-out and standard-err
053: * dsb.writeTo(System.out, System.err);
054: * </pre>
055: * <p>
056: * The internal representation has been optimized to make
057: * both appending and the <tt>writeTo(*)</tt> methods efficient
058: * (memory + time).
059: * <p>
060: * <b>NOTE:</b> this class is not internally thread-safe! If
061: * multiple threads need access to the same DualStreamBuffer
062: * instance then they must externally synchronized their
063: * actions. This class <i>does</i> internally support multiple
064: * simultaneous readers if there are zero simultaneous writers.
065: */
066: public final class DualStreamBuffer implements Serializable, Cloneable {
067:
068: //
069: // The data structure is a shared "byte[] mergedBuffer" for all
070: // the output, plus an "int[] offsetTable" for starting-point
071: // offsets within the mergedBuffer. All indices into the
072: // offsetTable follow this pattern:
073: // EVEN indices are standard-out
074: // ODD indices are standard-err
075: //
076:
077: private transient byte[] mergedBuffer;
078: private int mergedBufferLength;
079: private transient int[] offsetTable;
080: private int offsetTableLength;
081:
082: public DualStreamBuffer() {
083: this (89);
084: }
085:
086: public DualStreamBuffer(int size) {
087: if (size <= 0) {
088: throw new IllegalArgumentException("Invalid size: " + size);
089: }
090: mergedBuffer = new byte[size];
091: mergedBufferLength = 0;
092: offsetTable = new int[size];
093: offsetTableLength = 1;
094: }
095:
096: //
097: // For now there are only two "setter" methods, used
098: // for appending bytes from standard-out or standard-err:
099: //
100:
101: /**
102: * <pre>
103: * Get a stream for appending data either
104: * standard-out <tt>(isOut == true)</tt> or
105: * standard-error <tt>(isOut == false)</tt>.
106: * </pre>
107: */
108: public OutputStream getOutputStream(boolean isOut) {
109: return new InnerOutputStream(isOut);
110: }
111:
112: //
113: // These "getter" methods seem sufficient for now:
114: //
115:
116: /**
117: * Get an InputStream for both the
118: * standard-out <b>and</b> standard-error (interleaved in
119: * correct ordering).
120: *
121: * @see #writeTo(OutputStream)
122: */
123: public InputStream getInputStream() {
124: return new ByteArrayInputStream(mergedBuffer, 0,
125: mergedBufferLength);
126: }
127:
128: /**
129: * Get an InputStream for just standard-out
130: * <b>or</b> standard-error.
131: *
132: * @see #writeTo(OutputStream,boolean)
133: * @see #writeTo(OutputStream,OutputStream)
134: */
135: public InputStream getInputStream(boolean isOut) {
136: // not hard to code... let's wait until there's a need.
137: throw new UnsupportedOperationException();
138: }
139:
140: /**
141: * Write both the standard-out <b>and</b> standard-err
142: * (interleaved in correct ordering) to the given stream.
143: * <p>
144: * This is equivalent to fully reading a
145: * <tt>getInputStream()</tt>, but is more efficient.
146: */
147: public void writeTo(OutputStream os) throws IOException {
148: _write(os, 0, mergedBufferLength);
149: }
150:
151: /**
152: * Write either the standard-out <b>or</b> standard-error
153: * to the given stream.
154: * <p>
155: * Note that the ordering information relative to the
156: * other (out/err) is ignored, which is fine if you only
157: * care about the given (out/err) stream. See
158: * <tt>writeTo(Stream,Stream)</tt> for an alternative.
159: * <p>
160: * This is equivalent to fully reading a
161: * <tt>getInputStream(boolean)</tt>, but is more efficient.
162: */
163: public void writeTo(OutputStream os, boolean isOut)
164: throws IOException {
165: // based upon "writeTo(OutputStream,OutputStream)"
166: // can optimize by loop unrolling, then "i+=2" math...
167: // for now this is fine:
168: boolean currIsOut = true;
169: int currOff = 0;
170: for (int i = 0; i < offsetTableLength; i++) {
171: int nextOff = offsetTable[i];
172: int len = (nextOff - currOff);
173: if (currIsOut == isOut) {
174: _write(os, currOff, len);
175: }
176: currIsOut = !currIsOut;
177: currOff = nextOff;
178: }
179: }
180:
181: /**
182: * Write the standard-out <b>and (separately)</b> the
183: * standard-error to the given streams.
184: * <p>
185: * Each alternation of (out/err) is allowed to complete
186: * its write, which preserves the interleaving. This is
187: * handy when you need to display both streams in the
188: * correct order.
189: */
190: public void writeTo(OutputStream out, OutputStream err)
191: throws IOException {
192: boolean currIsOut = true;
193: int currOff = 0;
194: for (int i = 0; i < offsetTableLength; i++) {
195: int nextOff = offsetTable[i];
196: int len = (nextOff - currOff);
197: if (currIsOut) {
198: _write(out, currOff, len);
199: } else {
200: _write(err, currOff, len);
201: }
202: currIsOut = !currIsOut;
203: currOff = nextOff;
204: }
205: }
206:
207: //
208: // Index-based "getters" could be added here,
209: // e.g. "byte[] getBytes(int index) {}"
210: // but (for now) let's keep the API simple.
211: // One can always pass a ByteArrayOutputStream...
212: //
213:
214: /**
215: * Write from the "mergedBuffer" to the given stream.
216: * <p>
217: * May want to alter this code to guarantee that the
218: * stream can't modify the array (e.g. write byte-by-byte
219: * instead of passing the array itself).
220: */
221: private final void _write(OutputStream os, int off, int len)
222: throws IOException {
223: os.write(mergedBuffer, off, len);
224: }
225:
226: //
227: // serializers:
228: //
229:
230: /**
231: * Save the state of the <tt>DualStreamBuffer</tt> instance to a
232: * stream (that is, serialize it).
233: *
234: * @serialData the byte[] length, its byte elements,
235: * the int[] length, and its int elements.
236: */
237: private void writeObject(ObjectOutputStream s) throws IOException {
238: // write array lengths
239: s.defaultWriteObject();
240: // write mergedBuffer array
241: s.writeInt(mergedBuffer.length);
242: s.write(mergedBuffer, 0, mergedBufferLength);
243: // write offsetTable array
244: s.writeInt(offsetTable.length);
245: for (int i = 0; i < offsetTableLength; i++) {
246: s.writeInt(offsetTable[i]);
247: }
248: }
249:
250: /**
251: * Reconstitute the <tt>DualStreamBuffer</tt> instance from a
252: * stream (that is, deserialize it).
253: */
254: private void readObject(ObjectInputStream s) throws IOException,
255: ClassNotFoundException {
256: // read array lengths
257: s.defaultReadObject();
258: // read mergedBuffer array
259: int mbLen = s.readInt();
260: mergedBuffer = new byte[mbLen];
261: int len = mergedBufferLength;
262: for (int n = 0; n < len;) {
263: int count = s.read(mergedBuffer, n, len - n);
264: if (count < 0) {
265: throw new EOFException();
266: }
267: n += count;
268: }
269: // read offsetTable array
270: int otLen = s.readInt();
271: offsetTable = new int[otLen];
272: for (int i = 0; i < offsetTableLength; i++) {
273: offsetTable[i] = s.readInt();
274: }
275: // validate offsetTable contents?
276: }
277:
278: /**
279: * @return a clone of this <tt>DualStreamBuffer</tt> instance.
280: */
281: public Object clone() {
282: // since the buffer is add-only we may be able to cheat.
283: // However, we want the clone to "trim()" and save memory.
284: // For now let's do a real clone:
285: try {
286: DualStreamBuffer dsb = (DualStreamBuffer) super .clone();
287: dsb.mergedBuffer = new byte[mergedBufferLength];
288: System.arraycopy(mergedBuffer, 0, dsb.mergedBuffer, 0,
289: mergedBufferLength);
290: dsb.offsetTable = new int[offsetTableLength];
291: System.arraycopy(offsetTable, 0, dsb.offsetTable, 0,
292: offsetTableLength);
293: return dsb;
294: } catch (CloneNotSupportedException e) {
295: throw new InternalError("never!");
296: }
297: }
298:
299: //
300: // Inner classes for stream implementations:
301: //
302:
303: /**
304: * "setter" stream for appending.
305: */
306: private class InnerOutputStream extends OutputStream {
307: private final boolean isOut;
308:
309: public InnerOutputStream(boolean isOut) {
310: this .isOut = isOut;
311: }
312:
313: public void write(int b) {
314: // append
315: int mbIdx = mergedBufferLength;
316: growCapacity(1);
317: mergedBuffer[mbIdx] = (byte) b;
318: }
319:
320: public void write(byte b[]) {
321: write(b, 0, b.length);
322: }
323:
324: public void write(byte b[], int off, int len) {
325: if (b == null) {
326: throw new NullPointerException();
327: } else if ((off < 0) || (off > b.length) || (len < 0)) {
328: throw new IndexOutOfBoundsException();
329: }
330: int tail = (off + len);
331: if ((tail > b.length) || (tail < 0)) {
332: throw new IndexOutOfBoundsException();
333: } else if (len == 0) {
334: return;
335: }
336: // append
337: int mbIdx = mergedBufferLength;
338: growCapacity(len);
339: int i = off;
340: do {
341: mergedBuffer[mbIdx++] = b[i++];
342: } while (i < tail);
343: }
344:
345: // alters lengths and offset-table!
346: private void growCapacity(int len) {
347: int otIdx = offsetTableLength;
348: if (isOut != ((otIdx & 1) == 0)) {
349: otIdx--;
350: } else {
351: offsetTableLength++;
352: if (offsetTableLength >= offsetTable.length) {
353: // grow offsetTable
354: int[] oldOffsetTable = offsetTable;
355: offsetTable = new int[((offsetTableLength * 3) / 2 + 1)];
356: System.arraycopy(oldOffsetTable, 0, offsetTable, 0,
357: otIdx);
358: }
359: }
360: int mbIdx = mergedBufferLength;
361: mergedBufferLength += len;
362: if (mergedBufferLength >= mergedBuffer.length) {
363: // grow mergedBuffer
364: byte[] oldMergedBuffer = mergedBuffer;
365: mergedBuffer = new byte[((mergedBufferLength * 3) / 2 + 1)];
366: System.arraycopy(oldMergedBuffer, 0, mergedBuffer, 0,
367: mbIdx);
368: }
369: offsetTable[otIdx] = mergedBufferLength;
370: }
371: }
372:
373: private static final long serialVersionUID = 678782739879123215L;
374: }
|