// $Header: /p/sss/cvs/OpenVM/src/syslib/user/ovm_realtime/org/ovmj/java/io/BufferingPrintStream.java,v 1.2 2004/10/19 01:40:06 dholmes Exp $

package org.ovmj.java.io;

import java.io.*;
import org.ovmj.java.Opaque;
import gnu.java.io.*;
import gnu.java.io.encode.*;

/**
 * Implementation of a PrintStream that does its own buffering and can do all
 * print() and println() operations without allocating memory.  (The exception
 * is print(Object) and println(Object), where the user's toString() method
 * may allocate memory.)
 * <p>
 * Note that PrintStream is not an interface and contains a bunch of private
 * fields.  Hence, this class must re-implement everything in PrintStream
 * (including repeating many of those fields).  It's ugly, but that's what's
 * needed to make it work.
 *
 * @author Filip Pizlo
 */
public class BufferingPrintStream extends PrintStream {

    private boolean errorOccurred = false;
    private boolean closed = false;
    private boolean autoFlush;
    private Encoder encoder;
    private byte[] buffer;
    private int position = 0;
    
    BufferingPrintStream(OutputStream out,
                         boolean autoFlush,
                         Encoder encoder) {
        super(out);
        this.autoFlush = autoFlush;
        this.encoder = encoder;
        buffer = new byte[65536];
    }
    
    public BufferingPrintStream(OutputStream out) {
        this(out,false,EncodingManager.getEncoder());
    }
    
    public BufferingPrintStream(OutputStream out,
                                boolean autoFlush) {
        this(out,autoFlush,EncodingManager.getEncoder());
    }
    
    public BufferingPrintStream(OutputStream out,
                                boolean autoFlush,
                                String encoding)
        throws UnsupportedEncodingException {
        this(out,
             autoFlush,
             EncodingManager.getEncoder(out,
                                        encoding,
                                        false /* request no caching to ensure
                                                 scope safety! */));
    }
    
    public synchronized boolean checkError() {
        if (!closed) {
            flush();
        }
        
        return errorOccurred;
    }
    
    protected synchronized void setError() {
        errorOccurred=true;
    }
    
    public synchronized void close() {
        try {
            out.close();
        } catch (IOException e) {
            setError();
        }
        closed=true;
    }
    
    public synchronized void flush() {
        try {
            out.write(buffer,0,position);
            position=0;
            out.flush();
        } catch (IOException e) {
            setError();
        }
    }
    
    // don't need to override public void print(boolean bool)
    
    public synchronized void print(int inum) {
        Opaque area=LibraryImports.enterScratchPad();
        try {
            super.print(inum);
        } finally {
            LibraryImports.leaveArea(area);
        }
    }
    
    public synchronized void print(long lnum) {
        Opaque area=LibraryImports.enterScratchPad();
        try {
            super.print(lnum);
        } finally {
            LibraryImports.leaveArea(area);
        }
    }
    
    public synchronized void print(float fnum) {
        Opaque area=LibraryImports.enterScratchPad();
        try {
            super.print(fnum);
        } finally {
            LibraryImports.leaveArea(area);
        }
    }
    
    public synchronized void print(double dnum) {
        Opaque area=LibraryImports.enterScratchPad();
        try {
            super.print(dnum);
        } finally {
            LibraryImports.leaveArea(area);
        }
    }
    
    // not overriding print(Object)
    
    public synchronized void print(String str) {
        printImpl(LibraryImports.breakEncapsulation_String_value(str),
                  LibraryImports.breakEncapsulation_String_offset(str),
                  LibraryImports.breakEncapsulation_String_count(str));
    }
    
    public synchronized void print(char c) {
        Opaque area=LibraryImports.enterScratchPad();
        try {
            char[] ca=new char[]{c};
            print(ca);
        } finally {
            LibraryImports.leaveArea(area);
        }
    }
    
    void printImpl(char[] charArray,
                   int charArrayOffset,
                   int charArrayLength) {
        int bytesInCharArray;

        try {
            bytesInCharArray=encoder.bytesInCharArray(charArray,
                                                      charArrayOffset,
                                                      charArrayLength);
        } catch (CharConversionException e) {
            setError();
            return;
        }

        if (bytesInCharArray>buffer.length) {
            printImpl(charArray,
                      charArrayOffset,
                      charArrayLength/2);
            printImpl(charArray,
                      charArrayOffset+charArrayLength/2,
                      charArrayLength-charArrayLength/2);
            return;
        }

        if (bytesInCharArray>buffer.length-position) {
            flush();
        }
        
        try {
            encoder.convertToBytes(charArray,
                                   charArrayOffset,
                                   charArrayLength,
                                   buffer,
                                   position);
        } catch (CharConversionException e) {
            setError();
            return;
        }

        position+=bytesInCharArray;

        if (autoFlush) {
            for (int i = charArrayOffset; i < charArrayLength; i++) {
                if (charArray[i] == '\n') {
                    flush();
                    return;
                }
            }
        }


    }
    
    public synchronized void print(char[] charArray) {
        printImpl(charArray,0,charArray.length);
    }
    
    public synchronized void println() {
        write('\n');
    }
    
    // not overriding public void println(boolean bool)
    
    public synchronized void println(int inum) {
        print(inum);
        write('\n');
    }
    
    public synchronized void println(long lnum) {
        print(lnum);
        write('\n');
    }
    
    public synchronized void println(float fnum) {
        print(fnum);
        write('\n');
    }
    
    public synchronized void println(double dnum) {
        print(dnum);
        write('\n');
    }
    
    // not overriding public void println(Object)
    
    public synchronized void println(String str) {
        print(str);
        write('\n');
    }
    
    public synchronized void println(char ch) {
        print(ch);
        write('\n');
    }
    
    public synchronized void println(char[] charArray) {
        print(charArray);
        write('\n');
    }
    
    public synchronized void write(int oneByte) {
        if (position==buffer.length) {
            flush();
        }
        buffer[position++]=(byte)oneByte;
        if (autoFlush && oneByte=='\n') {
            flush();
        }
    }
    
    public synchronized void write(byte[] data,
                                   int dataOffset,
                                   int dataLen) {
        if (dataLen>buffer.length-position) {
            flush();
        }
        
        if (dataLen>buffer.length) {
            try {
                out.write(data,dataOffset,dataLen);
                out.flush();
            } catch (IOException e) {
                setError();
            }
            return;
        }
        
        System.arraycopy(data,dataOffset,
                         buffer,position,
                         dataLen);

        position+=dataLen;

        if (autoFlush) { // always flush on byte[]
            flush();
        }
    }
}

