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