// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)FastOutputStream.java, 1.22, 01/02/97





package marimba.io;





import java.io.*;


import marimba.persist.*;





/**


 * Fast unsynchronized buffered output stream. This stream combines the


 * functionality of DataOutputStream, PrintStream, and BufferedOutputStream.


 *


 * @author	Jonathan Payne


 * @author	Arthur van Hoff


 * @version 	1.22, 01/02/97


 */


public class FastOutputStream extends FilterOutputStream implements DataOutput {


    private static int nln;


    private static byte nl0;


    private static byte nl1;





    BinaryPersistentState state;


    boolean error;


    protected byte buf[];


    protected int count;


    public OutputStream out;





    /**


     * Open a file for writing.


     */


    public FastOutputStream(String file) throws IOException {


	this(new FileOutputStream(file), 4 * 1024);


    }





    /**


     * Open a file for writing.


     */


    public FastOutputStream(File file) throws IOException {


	this(new FileOutputStream(file), 4 * 1024);


    }





    /**


     * Open using a random access file.


     */


    public FastOutputStream(RandomAccessFile file) throws IOException {


	this(new RAFOutputStream(file), 4 * 1024);


    }





    /**


     * Combine two streams.


     */


    public FastOutputStream(OutputStream out) {


	this(out, 4 * 1024);


    }





    /**


     * Combine two streams given a buffer size.


     */


    public FastOutputStream(OutputStream out, int size) {


	super(out);


	this.out = out;


	buf = new byte[size];


    }





    /**


     * Create a memory output stream with a given size.


     */


    public FastOutputStream() {


	this(4*1024);


    }





    /**


     * Create a memory output stream with a given size.


     */


    public FastOutputStream(int siz) {


	super(null);


	buf = new byte[siz];


    }





    /**


     * Create a memory output stream with a given size.


     */


    public FastOutputStream(byte buf[]) {


	super(null);


	this.buf = buf;


    }





    /**


     * Return true if there has been a write error.


     * I/O erros are not reported during writes. Instead


     * the are reported when you flush or close the stream.


     */


    public boolean getError() {


	return error;


    }





    public final void write(int b) {


	if (count == buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)b;


    }





    public final void write(byte b[], int off, int len) {


	int avail = buf.length - count;





	while (len > avail) {


	    System.arraycopy(b, off, buf, count, avail);


	    off += avail;


	    len -= avail;


	    count += avail;


	    flushBuffer();


	    avail = buf.length - count;


	}


	if (len > 0) {


	    System.arraycopy(b, off, buf, count, len);


	    count += len;


	}


    }





    public final void flush() throws IOException {


	if (out != null) {


	    flushBuffer();


	    out.flush();


	}


	if (error) {


	    throw new IOException("output error");


	}


    }





    private void flushBuffer() {


	if (out == null) {


	    // This is a memory buffer, just grow the buffer!


	    byte newbuf[] = new byte[buf.length*2];


	    System.arraycopy(buf, 0, newbuf, 0, count);


	    buf = newbuf;


	    return;


	}


	try {


	    int limit = count;


	    out.write(buf, 0, limit);


	} catch (IOException e) {


	    error = true;


	}


	count = 0;


    }





    public void close() throws IOException {


	if (out != null) {


	    super.close();


	}


	if (error) {


	    throw new IOException("error on close");


	}


    }





    /** Version of close which doesn't throw an exception. */


    public void justClose() {


	try {


	    out.close();


	} catch (IOException e) {


	    error = true;


	}


    }





    public int size() {


	if (out != null) {


	    throw new RuntimeException("not a memory stream");


	}


	return count;


    }





    public byte getByteArray()[] {


	return buf;


    }





    public byte[] toByteArray() {


	if (out != null) {


	    throw new RuntimeException("not a memory stream");


	}


	byte buf[] = getByteArray();


	byte newbuf[] = new byte[count];


	System.arraycopy(buf, 0, newbuf, 0, count);


	return newbuf;


    }





    public String toString() {


	if (out == null) {


	    return new String(buf, 0, 0, count);


	}


	return getClass().getName() + "[count=" + count + "]";


    }





    public final void writeBoolean(boolean v) {


	if (count + 1 > buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)(v ? 1 : 0);


    }





    public final void writeByte(int v) {


	if (count + 1 > buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)v;


    }





    public final void writeShort(int v) {


	if (count + 2 > buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)((v >>> 8) & 0xFF);


	buf[count++] = (byte)((v >>> 0) & 0xFF);


    }





    public final void writeChar(int v) {


	if (count + 2 > buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)((v >>> 8) & 0xFF);


	buf[count++] = (byte)((v >>> 0) & 0xFF);


    }





    public final void writeInt(int v) {


	if (count + 4 > buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)((v >>> 24) & 0xFF);


	buf[count++] = (byte)((v >>> 16) & 0xFF);


	buf[count++] = (byte)((v >>>  8) & 0xFF);


	buf[count++] = (byte)((v >>>  0) & 0xFF);


    }





    public final void writeLong(long v) {


	if (count + 8 > buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)((v >>> 56) & 0xFF);


	buf[count++] = (byte)((v >>> 48) & 0xFF);


	buf[count++] = (byte)((v >>> 40) & 0xFF);


	buf[count++] = (byte)((v >>> 32) & 0xFF);


	buf[count++] = (byte)((v >>> 24) & 0xFF);


	buf[count++] = (byte)((v >>> 16) & 0xFF);


	buf[count++] = (byte)((v >>>  8) & 0xFF);


	buf[count++] = (byte)((v >>>  0) & 0xFF);


    }





    public final void writeFloat(float v) {


	writeInt(Float.floatToIntBits(v));


    }





    public final void writeDouble(double v) {


	writeLong(Double.doubleToLongBits(v));


    }





    public final void writeBytes(String s) throws IOException {


	print(s);


    }





    public final void writeChars(String s) throws IOException {


	int len = s.length();


	for (int i = 0 ; i < len ; i++) {


	    int v = s.charAt(i);


	    write((v >>> 8) & 0xFF);


	    write((v >>> 0) & 0xFF);


	}


    }





    public final void writeUTF(String str) {


	int strlen = str.length();


	int utflen = 0;





	for (int i = 0 ; i < strlen ; i++) {


	    int c = str.charAt(i);


	    if ((c >= 0x0001) && (c <= 0x007F)) {


		utflen++;


	    } else if (c > 0x07FF) {


		utflen += 3;


	    } else {


		utflen += 2;


	    }


	}





	write((utflen >>> 8) & 0xFF);


	write((utflen >>> 0) & 0xFF);


	for (int i = 0 ; i < strlen ; i++) {


	    int c = str.charAt(i);


	    if ((c >= 0x0001) && (c <= 0x007F)) {


		write(c);


	    } else if (c > 0x07FF) {


		write(0xE0 | ((c >> 12) & 0x0F));


		write(0x80 | ((c >>  6) & 0x3F));


		write(0x80 | ((c >>  0) & 0x3F));


	    } else {


		write(0xC0 | ((c >>  6) & 0x1F));


		write(0x80 | ((c >>  0) & 0x3F));


	    }


	}


    }





    public final void writeObject(PropertyObject obj) {


	if (state == null) {


	    state = new BinaryPersistentState();


	}


	PersistentObject frozen = state.freeze(obj);


	state.save(this, frozen);


    }





    public final void print(char ch) {


	if (count == buf.length) {


	    flushBuffer();


	}


	buf[count++] = (byte)ch;


    }





    public final void println(char ch) {


	print(ch);


	println();


    }





    public final void print(String str) {


	if (str == null) {


	    str = "null";


	}





	int avail = buf.length - count;


	int len = str.length();


	int off = 0;





	while (len > avail) {


	    str.getBytes(off, off + avail, buf, count);


	    off += avail;


	    len -= avail;


	    count += avail;


	    flushBuffer();


	    avail = buf.length;


	}





	str.getBytes(off, off + len, buf, count);


	count += len;


    }


    public final void println(String str) {


	print(str);


	println();


    }





    public final void print(Object obj) {


	print(String.valueOf(obj));


    }


    public final void println(Object obj) {


	print(obj);


	println();


    }





    private void printDigit(long n) {


	if (n >= 10) {


	    printDigit(n / 10);


	}


	buf[count++] = (byte)('0' + (n%10));


    }





    public final void print(long n) {


	if (count + 32 > buf.length) {


	    flushBuffer();


	}


	if (n < 0) {


	    buf[count++] = '-';


	    n = -n;


	}


	printDigit(n);


    }


    public final void println(long n) {


	print(n);


	println();


    }





    static {


	String newline = System.getProperty("line.separator");


	nln = newline.length();


	nl0 = (byte)newline.charAt(0);


	if (nln > 1) {


	    nl1 = (byte)newline.charAt(1);


	}


    }





    public final void println() {


	if (count + 2 >= buf.length) {


	    flushBuffer();


	}


	buf[count++] = nl0;


	if (nln == 2) {


	    buf[count++] = nl1;


	}


    }








    public void seek(long p) throws IOException {


	flush();


	((RAFOutputStream)out).raf.seek(p);


	//System.out.println("SEEK: " + p);


    }





    public long getFilePointer() throws IOException {


	return ((RAFOutputStream)out).raf.getFilePointer() + count;


    }


}


