/*
Copyright (C) 2002 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.mysql.jdbc;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
/**
* @author Mark Matthews
*
* To change this generated comment edit the template variable "typecomment":
* Window>Preferences>Java>Templates.
* To enable and disable the creation of type comments go to
* Window>Preferences>Java>Code Generation.
*/
class CompressedInputStream extends InputStream {
//~ Instance fields --------------------------------------------------------
/**
* The ZIP inflater used to un-compress packets
*/
private Inflater inflater;
/**
* The stream we are reading from the server
*/
private InputStream in;
/**
* The packet data after it has been un-compressed
*/
private byte[] uncompressedPacket;
/**
* The position we are reading from
*/
private int pos = 0;
//~ Constructors -----------------------------------------------------------
/**
* Creates a new CompressedInputStream that reads
* the given stream from the server.
*
* @param streamFromServer
*/
public CompressedInputStream(InputStream streamFromServer) {
this.in = streamFromServer;
this.inflater = new Inflater();
}
//~ Methods ----------------------------------------------------------------
/**
* @see java.io.InputStream#available()
*/
public int available() throws IOException {
if (this.uncompressedPacket == null) {
return this.in.available();
}
return this.uncompressedPacket.length - this.pos + this.in.available();
}
/**
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
this.in.close();
this.uncompressedPacket = null;
this.inflater = null;
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
try {
getNextPacketIfRequired(1);
} catch (IOException ioEx) {
return -1;
}
return this.uncompressedPacket[this.pos++] & 0xff;
}
/**
* @see java.io.InputStream#read(byte, int, int)
*/
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0)
|| ((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
if (len <= 0) {
return 0;
}
try {
getNextPacketIfRequired(len);
} catch (IOException ioEx) {
return -1;
}
System.arraycopy(this.uncompressedPacket, this.pos, b, off, len);
this.pos += len;
return len;
}
/**
* @see java.io.InputStream#read(byte)
*/
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* @see java.io.InputStream#skip(long)
*/
public long skip(long n) throws IOException {
long count = 0;
for (long i = 0; i < n; i++) {
int bytesRead = read();
if (bytesRead == -1) {
break;
}
count++;
}
return count;
}
/**
* Retrieves and un-compressed (if necessary) the next
* packet from the server.
*
* @throws IOException if an I/O error occurs
*/
private void getNextPacketFromServer() throws IOException {
byte[] uncompressedBuffer = null;
int packetLength = this.in.read() + (this.in.read() << 8)
+ (this.in.read() << 16);
// -1 for all values through above assembly sequence
if (packetLength == -65793) {
throw new IOException("Unexpected end of input stream");
}
// we don't look at packet sequence in this case
this.in.read();
int compressedLength = this.in.read() + (this.in.read() << 8)
+ (this.in.read() << 16);
if (compressedLength > 0) {
uncompressedBuffer = new byte[compressedLength];
byte[] compressedBuffer = new byte[packetLength];
readFully(compressedBuffer, 0, packetLength);
try {
this.inflater.reset();
} catch (NullPointerException npe) {
this.inflater = new Inflater();
}
this.inflater.setInput(compressedBuffer);
try {
this.inflater.inflate(uncompressedBuffer);
} catch (DataFormatException dfe) {
throw new IOException(
"Error while uncompressing packet from server.");
}
this.inflater.end();
} else {
//
// Read data, note this this code is reached when using
// compressed packets that have not been compressed, as well
//
uncompressedBuffer = new byte[packetLength + 1];
readFully(uncompressedBuffer, 0, packetLength);
}
if ((this.uncompressedPacket != null)
&& (this.pos < this.uncompressedPacket.length)) {
int remainingLength = this.uncompressedPacket.length - this.pos;
byte[] combinedBuffer = new byte[remainingLength
+ uncompressedBuffer.length];
System.arraycopy(this.uncompressedPacket, this.pos, combinedBuffer, 0,
remainingLength);
System.arraycopy(uncompressedBuffer, 0, combinedBuffer,
remainingLength, uncompressedBuffer.length);
uncompressedBuffer = combinedBuffer;
}
this.uncompressedPacket = uncompressedBuffer;
this.pos = 0;
return;
}
/**
* Determines if another packet needs to be read from the server
* to be able to read numBytes from the stream.
*
* @param numBytes the number of bytes to be read
* @throws IOException if an I/O error occors.
*/
private void getNextPacketIfRequired(int numBytes)
throws IOException {
if ((this.uncompressedPacket == null)
|| ((this.pos + numBytes) > this.uncompressedPacket.length)) {
getNextPacketFromServer();
}
}
private final int readFully(byte[] b, int off, int len)
throws IOException {
if (len < 0) {
throw new IndexOutOfBoundsException();
}
int n = 0;
while (n < len) {
int count = this.in.read(b, off + n, len - n);
if (count < 0) {
throw new EOFException();
}
n += count;
}
return n;
}
}