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


// @(#)FastInputStream.java, 1.28, 01/02/97





package marimba.io;





import java.io.*;


import marimba.persist.*;





/**


 * Fast unsynchronized buffered input stream. This stream combines a lot


 * of the functionality of BufferedInputStream, ByteArrayInputStream,


 * and DataInputStream.


 *


 * @author	Jonathan Payne


 * @author	Arthur van Hoff


 * @version 	1.28, 01/02/97


 */


public class FastInputStream extends FilterInputStream implements DataInput {


    BinaryPersistentState state;


    boolean error;


    protected byte buf[];


    protected int count;


    protected int pos;


    protected int markpos;


    char lineBuffer[];





    /**


     * Open a file.


     */


    public FastInputStream(String file) throws IOException {


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


    }





    /**


     * Open a file.


     */


    public FastInputStream(File file) throws IOException {


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


    }





    /**


     * Access a RandomAccessFile


     */


    public FastInputStream(RandomAccessFile in) {


	this(new RAFInputStream(in), 4 * 1024);


    }





    /**


     * Combine two streams.


     */


    public FastInputStream(InputStream in) {


	this(in, 4 * 1024);


    }





    /**


     * Combine two streams given a buffer size.


     */


    public FastInputStream(InputStream in, int size) {


	super(in);


	buf = new byte[size];


    }





    /**


     * Create a stream that reads from a buffer.


     */


    public FastInputStream(byte buf[]) {


	this(buf, 0, buf.length);


    }





    /**


     * Create a stream that reads from a buffer.


     */


    public FastInputStream(byte buf[], int off, int len) {


	super(null);


	this.buf = buf;


	this.pos = off;


	this.count = off + len;


    }





    /**


     * See if there has been an I/O error. I/O errors


     * are not reported during reads. Instead they are


     * reported when the stream is closed.


     */


    public boolean getError() {


	return error;


    }





    /**


     * Backup by a given amount. This will throw an


     * IOexception if you can't backup enough. You can


     * use this only to backup 1 character, or some small


     * amount at the beginning of a stream.


     */


    public boolean backup(int n) {


	if (pos >= n) {


	    pos -= n;


	    return true;


	}


	return false;


    }





    private boolean fill() {


	if (in == null) {


	    return false;


	}


	if (count > pos) {


	    System.arraycopy(buf, pos, buf, 0, count - pos);


	}


	count -= pos;


	pos = 0;





	if ((!error) && (count < buf.length)) {


	    try {


		int n = in.read(buf, count, buf.length - count);


		if (n >= 0) {


		    count += n;


		    return true;


		}


	    } catch (IOException e) {


		error = true;


	    }


	}


	return false;


    }





    private boolean fill(int n) {


	while (count - pos < n && fill())


	    ;


	return (count - pos >= n);


    }





    public int read() {


	if (pos < count)


	    return (buf[pos++] & 0xFF);


	return fill(1) ? (buf[pos++] & 0xFF) : -1;


    }





    public int read(byte b[], int off, int len) {


	int avail = count - pos;


	if (avail <= 0) {


	    fill();


	    avail = count - pos;


	    if (avail <= 0) {


		return -1;


	    }


	}


	int cnt = (avail < len) ? avail : len;


	System.arraycopy(buf, pos, b, off, cnt);


	pos += cnt;


	return cnt;


    }





    public long skip(long n) {


	long avail = count - pos;





	if (avail <= 0) {


	    fill();


	    avail = count - pos;


	    if (avail <= 0) {


		return -1;


	    }


	}





	if (n <= avail) {


	    pos += n;


	    return n;


	}





	count = pos = 0;


	n -= avail;


	return avail;


    }





    public int available() throws IOException {


	return (count - pos) + ((in != null) ? in.available() : 0);


    }





    public void close() throws IOException {


	if (in != null) {


	    super.close();


	}


	if (error) {


	    throw new IOException("input error");


	}


    }





    public void readFully(byte b[]) {


	readFully(b, 0, b.length);


    }





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


	int n = 0;


	while (n < len) {


	    int count = read(b, off + n, len - n);





	    if (count < 0) {


		error = true;


		return;


	    }


	    n += count;


	}


    }





    public int skipBytes(int n) {


	for (int i = 0 ; i < n ; i += (int)skip(n - i));


	return n;


    }





    public boolean readBoolean() {


	return read() != 0;


    }





    public byte readByte() {


	if (pos < count)


	    return buf[pos++];


	return fill(1) ? buf[pos++] : -1;


    }





    public int readUnsignedByte() {


	return read();


    }





    public short readShort() {


	if (count - pos < 2 && !fill(2))


	    return 0;


	return (short) (((buf[pos++] & 0xFF) << 8) + (buf[pos++] & 0xFF));


    }





    public int readUnsignedShort() {


	if (count - pos < 2 && !fill(2))


	    return 0;


	return ((buf[pos++] & 0xFF) << 8) + (buf[pos++] & 0xFF);


    }





    public char readChar() {


	if (count - pos < 2 && !fill(2))


	    return 0;


	return (char) (((buf[pos++] & 0xFF) << 8) + (buf[pos++] & 0xFF));


    }





    public int readInt() {


	if (count - pos < 4 && !fill(4))


	    return 0;


	byte buf[] = this.buf;


	return ((buf[pos++] & 0xFF) << 24) + ((buf[pos++] & 0xFF) << 16) + ((buf[pos++] & 0xFF) << 8) + (buf[pos++] & 0xFF);


    }





    public long readLong() {


	if (count - pos < 8 && !fill(8))


	    return 0;


	byte buf[] = this.buf;


	return ((buf[pos++] & 0xFF) << 56L) + ((buf[pos++] & 0xFF) << 48L) + ((buf[pos++] & 0xFF) << 40L) + ((buf[pos++] & 0xFF) << 32L) + ((buf[pos++] & 0xFF) << 24L) + ((buf[pos++] & 0xFF) << 16L) + ((buf[pos++] & 0xFF) << 8L) + (buf[pos++] & 0xFF);


    }





    public float readFloat() {


	return Float.intBitsToFloat(readInt());


    }





    public double readDouble() {


	return Double.longBitsToDouble(readLong());


    }





    public String readLine() {


	int len = 0, c;


	char buf[] = lineBuffer;





	if (buf == null) {


	    buf = lineBuffer = new char[128];


	}





	while (true) {


	    switch (c = read()) {


	      case -1:


		return (len == 0) ? null : new String(buf, 0, len);





	      case '\r':


		if ((c = read()) != '\n') {


		    backup(1);


		}


	      case '\n':


		return new String(buf, 0, len);





	      default:


		if (len == buf.length) {


		    lineBuffer = new char[len * 2];


		    System.arraycopy(buf, 0, lineBuffer, 0, len);


		    buf = lineBuffer;


		}


		buf[len++] = (char)c;


	    }


	}


    }





    public String readUTF() {


	if (count - pos < 2 && !fill(2))


	    return null;


        int utflen = ((buf[pos++] & 0xFF) << 8) + (buf[pos++] & 0xFF);


        char str[] = new char[utflen];


	int count = 0;


	int strlen = 0;


	try {


	    while (count < utflen) {


		int c;


		if (pos == this.count && !fill(1))


		    return null;


		c = buf[pos++] & 0xFF;


		int char2, char3;


		switch (c >> 4) { 


		    case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:


			// 0xxxxxxx


			count++;


			str[strlen++] = (char)c;


			break;


		    case 12: case 13:


			// 110x xxxx   10xx xxxx


			count += 2;


			if (count > utflen) 


			    throw new UTFDataFormatException();		  


			char2 = read();


			if ((char2 & 0xC0) != 0x80)


			    throw new UTFDataFormatException();		  


			str[strlen++] = (char)(((c & 0x1F) << 6) | (char2 & 0x3F));


			break;


		    case 14:


			// 1110 xxxx  10xx xxxx  10xx xxxx


			count += 3;


			if (count > utflen) 


			    throw new UTFDataFormatException();		  


			char2 = read();


			char3 = read();


			if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))


			    throw new UTFDataFormatException();		  


			str[strlen++] = (char)(((c & 0x0F) << 12) |


					       ((char2 & 0x3F) << 6) |


					       ((char3 & 0x3F) << 0));


		    default:


			// 10xx xxxx,  1111 xxxx


			throw new UTFDataFormatException();		  


		    }


	    }


	    return new String(str, 0, strlen);


	} catch (IOException e) {


	    error = true;


	    return null;


	}


    }





    public PropertyObject readObject() {


	if (state == null) {


	    state = new BinaryPersistentState();


	}


	try {


	    PersistentObject frozen = state.loadPersistentObject(this);


	    return state.thaw(frozen);


	} catch (SyntaxError e) {


	    e.printStackTrace();


	    error = true;


	    return null;


	}


    }





    public boolean markSupported() {


	return true;


    }





    public void mark(int limit) {


	markpos = pos;


    }





    public void reset() {


	pos = markpos;


    }





    public void seek(long p) throws IOException {


	count = pos = markpos = 0;


	((RAFInputStream)in).in.seek(p);


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


    }





    public long getFilePointer() throws IOException {


	return ((RAFInputStream)in).in.getFilePointer() - (count - pos);


    }


}


