Source code: com/mysql/jdbc/CompressedInputStream.java
1 /*
2 Copyright (C) 2002-2004 MySQL AB
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as
6 published by the Free Software Foundation.
7
8
9 There are special exceptions to the terms and conditions of the GPL
10 as it is applied to this software. View the full text of the
11 exception exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
12 software distribution.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 */
24 package com.mysql.jdbc;
25
26 import java.io.EOFException;
27 import java.io.IOException;
28 import java.io.InputStream;
29
30 import java.util.zip.DataFormatException;
31 import java.util.zip.Inflater;
32
33
34 /**
35 * Used to de-compress packets from the MySQL server when protocol-level
36 * compression is turned on.
37 *
38 * @author Mark Matthews
39 *
40 * @version $Id: CompressedInputStream.java,v 1.1.4.5 2004/08/09 22:15:11 mmatthew Exp $
41 */
42 class CompressedInputStream extends InputStream {
43
44 /** The ZIP inflater used to un-compress packets */
45 private Inflater inflater;
46
47 /** The stream we are reading from the server */
48 private InputStream in;
49
50 /** The packet data after it has been un-compressed */
51 private byte[] buffer;
52
53 /** The position we are reading from */
54 private int pos = 0;
55
56 /**
57 * The buffer to read packet headers into
58 */
59 private byte[] packetHeaderBuffer = new byte[7];
60
61 /**
62 * Creates a new CompressedInputStream that reads the given stream from the
63 * server.
64 *
65 * @param conn DOCUMENT ME!
66 * @param streamFromServer
67 */
68 public CompressedInputStream(InputStream streamFromServer) {
69 this.in = streamFromServer;
70 this.inflater = new Inflater();
71 }
72
73 /**
74 * @see java.io.InputStream#available()
75 */
76 public int available() throws IOException {
77 if (this.buffer == null) {
78 return this.in.available();
79 }
80
81 return this.buffer.length - this.pos + this.in.available();
82 }
83
84 /**
85 * @see java.io.InputStream#close()
86 */
87 public void close() throws IOException {
88 this.in.close();
89 this.buffer = null;
90 this.inflater = null;
91 }
92
93 /**
94 * @see java.io.InputStream#read()
95 */
96 public int read() throws IOException {
97 try {
98 getNextPacketIfRequired(1);
99 } catch (IOException ioEx) {
100 return -1;
101 }
102
103 return this.buffer[this.pos++] & 0xff;
104 }
105
106 /**
107 * @see java.io.InputStream#read(byte, int, int)
108 */
109 public int read(byte[] b, int off, int len) throws IOException {
110 if (b == null) {
111 throw new NullPointerException();
112 } else if ((off < 0) || (off > b.length) || (len < 0)
113 || ((off + len) > b.length) || ((off + len) < 0)) {
114 throw new IndexOutOfBoundsException();
115 }
116
117 if (len <= 0) {
118 return 0;
119 }
120
121 try {
122 getNextPacketIfRequired(len);
123 } catch (IOException ioEx) {
124 return -1;
125 }
126
127 System.arraycopy(this.buffer, this.pos, b, off, len);
128 this.pos += len;
129
130 return len;
131 }
132
133 /**
134 * @see java.io.InputStream#read(byte)
135 */
136 public int read(byte[] b) throws IOException {
137 return read(b, 0, b.length);
138 }
139
140 /**
141 * @see java.io.InputStream#skip(long)
142 */
143 public long skip(long n) throws IOException {
144 long count = 0;
145
146 for (long i = 0; i < n; i++) {
147 int bytesRead = read();
148
149 if (bytesRead == -1) {
150 break;
151 }
152
153 count++;
154 }
155
156 return count;
157 }
158
159 /**
160 * Retrieves and un-compressed (if necessary) the next packet from the
161 * server.
162 *
163 * @throws IOException if an I/O error occurs
164 */
165 private void getNextPacketFromServer() throws IOException {
166 byte[] uncompressedData = null;
167
168 int lengthRead = readFully(this.packetHeaderBuffer, 0,
169 7);
170
171 if (lengthRead < 7) {
172 throw new IOException("Unexpected end of input stream");
173 }
174
175 int compressedPacketLength = ((int) (this.packetHeaderBuffer[0] & 0xff))
176 + (((int) (this.packetHeaderBuffer[1] & 0xff)) << 8)
177 + (((int) (this.packetHeaderBuffer[2] & 0xff)) << 16);
178
179 int uncompressedLength = ((int) (this.packetHeaderBuffer[4] & 0xff))
180 + (((int) (this.packetHeaderBuffer[5] & 0xff)) << 8)
181 + (((int) (this.packetHeaderBuffer[6] & 0xff)) << 16);
182
183 if (uncompressedLength > 0) {
184 uncompressedData = new byte[uncompressedLength];
185
186 byte[] compressedBuffer = new byte[compressedPacketLength];
187
188 readFully(compressedBuffer, 0, compressedPacketLength);
189
190 try {
191 this.inflater.reset();
192 } catch (NullPointerException npe) {
193 this.inflater = new Inflater();
194 }
195
196 this.inflater.setInput(compressedBuffer);
197
198 try {
199 this.inflater.inflate(uncompressedData);
200 } catch (DataFormatException dfe) {
201 throw new IOException(
202 "Error while uncompressing packet from server.");
203 }
204
205 this.inflater.end();
206 } else {
207
208 //
209 // Read data, note this this code is reached when using
210 // compressed packets that have not been compressed, as well
211 //
212 uncompressedData = new byte[compressedPacketLength];
213 readFully(uncompressedData, 0, compressedPacketLength);
214 }
215
216 if ((this.buffer != null) && (this.pos < this.buffer.length)) {
217
218 int remaining = buffer.length - this.pos;
219 byte[] newBuffer = new byte[remaining + uncompressedData.length];
220
221 int newIndex = 0;
222
223 for (int i = this.pos; i < this.buffer.length; i++)
224 newBuffer[newIndex++] = this.buffer[i];
225
226 System.arraycopy(uncompressedData, 0, newBuffer, newIndex,
227 uncompressedData.length);
228
229 uncompressedData = newBuffer;
230 }
231
232 this.pos = 0;
233 this.buffer = uncompressedData;
234
235 return;
236 }
237
238 /**
239 * Determines if another packet needs to be read from the server to be able
240 * to read numBytes from the stream.
241 *
242 * @param numBytes the number of bytes to be read
243 *
244 * @throws IOException if an I/O error occors.
245 */
246 private void getNextPacketIfRequired(int numBytes)
247 throws IOException {
248 if ((this.buffer == null)
249 || ((this.pos + numBytes) > this.buffer.length)) {
250 getNextPacketFromServer();
251 }
252 }
253
254 private final int readFully(byte[] b, int off, int len)
255 throws IOException {
256 if (len < 0) {
257 throw new IndexOutOfBoundsException();
258 }
259
260 int n = 0;
261
262 while (n < len) {
263 int count = this.in.read(b, off + n, len - n);
264
265 if (count < 0) {
266 throw new EOFException();
267 }
268
269 n += count;
270 }
271
272 return n;
273 }
274 }