forked from microsoft/mssql-jdbc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathReaderInputStream.java
271 lines (227 loc) · 11.4 KB
/
ReaderInputStream.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/*
* Microsoft JDBC Driver for SQL Server
*
* Copyright(c) Microsoft Corporation All rights reserved.
*
* This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/
package com.microsoft.sqlserver.jdbc;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.text.MessageFormat;
/**
* InputStream adapter for Readers.
*
* This class implements an InputStream whose bytes are encoded character values that are read on demand from a wrapped Reader using the suplied
* Charset.
*
* Character values pass through through the following in their transformation to bytes: Reader .. CharBuffer .. Charset (CharsetEncoder) ..
* ByteBuffer .. InputStream
*
* To minimize memory usage, the CharBuffer and ByteBuffer instances used by this class are created on demand when InputStream read methods are
* called.
*/
class ReaderInputStream extends InputStream {
// The Reader that this ReaderInputStream adapts.
private final Reader reader;
// The character set used to encode character values as
// they are read from the stream.
private final Charset charset;
// Length of the Reader, if known, in characters
private final long readerLength;
// Count of characters read from the reader across all calls to encodeChars()
private long readerCharsRead = 0;
// Flag indicating whether the stream has reached the end of its data
private boolean atEndOfStream = false;
// Internal character buffer used to transfer character values
// between the Reader and the Charset encoder.
private CharBuffer rawChars = null;
private static final int MAX_CHAR_BUFFER_SIZE = DataTypes.SHORT_VARTYPE_MAX_CHARS;
// Most recent set of bytes that were encoded from rawChars.
// This value is null initially and when the end of stream is reached.
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private ByteBuffer encodedChars = EMPTY_BUFFER;
ReaderInputStream(Reader reader,
Charset charset,
long readerLength) {
assert reader != null;
assert charset != null;
assert DataTypes.UNKNOWN_STREAM_LENGTH == readerLength || readerLength >= 0;
this.reader = reader;
this.charset = charset;
this.readerLength = readerLength;
}
/**
* Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this
* input stream.
*
* @return - the number of bytes that can be read from this input stream without blocking
* @throws IOException
* - if an I/O error occurs
*/
public int available() throws IOException {
assert null != reader;
assert null != encodedChars;
// If we know the reader to be empty, then take the short cut
if (0 == readerLength)
return 0;
// If there are encoded characters remaining in the buffer then that's our best guess.
if (encodedChars.remaining() > 0)
return encodedChars.remaining();
// If there are no encoded characters left in the buffer (or the buffer hasn't yet been populated)
// then ask the Reader whether a call to its read() method would block. If reading wouldn't block,
// then there's at least 1 byte that can be encoded, possibly more.
if (reader.ready())
return 1;
// If there are no encoded characters, and reading characters from the underlying Reader object
// would block, then nothing (more) can be read from this stream without blocking.
return 0;
}
private final byte[] oneByte = new byte[1];
public int read() throws IOException {
return (-1 == readInternal(oneByte, 0, oneByte.length)) ? -1 : oneByte[0];
}
public int read(byte[] b) throws IOException {
return readInternal(b, 0, b.length);
}
public int read(byte[] b,
int off,
int len) throws IOException {
return readInternal(b, off, len);
}
private int readInternal(byte[] b,
int off,
int len) throws IOException {
assert null != b;
assert 0 <= off && off <= b.length;
assert 0 <= len && len <= b.length;
assert off <= b.length - len;
if (0 == len)
return 0;
int bytesRead = 0;
while (bytesRead < len && encodeChars()) {
// Read the lesser of the number of bytes remaining
// in the encoded character buffer and the number
// of bytes remaining for this read request.
int bytesToRead = encodedChars.remaining();
if (bytesToRead > len - bytesRead)
bytesToRead = len - bytesRead;
// We should actually be attempting to read something here,
// or we'll be in an infinite loop...
assert bytesToRead > 0;
encodedChars.get(b, off + bytesRead, bytesToRead);
bytesRead += bytesToRead;
}
// Return number of bytes read, which may be less than
// the number of bytes requested, or -1 at end of stream.
return (0 == bytesRead && atEndOfStream) ? -1 : bytesRead;
}
/**
* Determines whether encoded characters are available, encoding them on demand by reading them from the reader as necessary.
*
* @return true when encoded characters are available
* @return false when no more encoded characters are available (i.e. end of stream)
* @exception IOException
* if an I/O error occurs reading from the reader or encoding the characters
*/
private boolean encodeChars() throws IOException {
// Once at the end of the stream, no more characters can be encoded.
if (atEndOfStream)
return false;
// Not at end of stream; check whether there are any encoded characters
// remaining in the byte buffer. If there are, don't encode any more
// characters this time.
if (encodedChars.hasRemaining())
return true;
// Encoded byte buffer is either exhausted or has never been filled
// (i.e. first time through). In that case, we need to repopulate
// the encoded character buffer by encoding raw characters.
//
// To do that, there needs to be raw characters available to encode.
// If there are no raw characters available (because the raw character
// buffer has been exhausted or never filled), then try to read in
// raw characters from the reader.
if (null == rawChars || !rawChars.hasRemaining()) {
if (null == rawChars) {
assert MAX_CHAR_BUFFER_SIZE <= Integer.MAX_VALUE;
rawChars = CharBuffer.allocate((DataTypes.UNKNOWN_STREAM_LENGTH == readerLength || readerLength > MAX_CHAR_BUFFER_SIZE)
? MAX_CHAR_BUFFER_SIZE : Math.max((int) readerLength, 1));
}
else {
// Flip the buffer to be ready for put (reader read) operations.
rawChars.clear();
}
// Try to fill up the raw character buffer by reading available characters
// from the reader into it.
//
// This loop continues until one of the following conditions is satisfied:
// - the raw character buffer has been filled,
// - the reader reaches end-of-stream
// - the reader throws any kind of Exception (driver throws an IOException)
// - the reader violates its interface contract (driver throws an IOException)
while (rawChars.hasRemaining()) {
int lastPosition = rawChars.position();
int charsRead = 0;
// Try reading from the app-supplied Reader
try {
charsRead = reader.read(rawChars);
}
// Catch any kind of exception and translate it to an IOException.
// The app-supplied reader cannot be trusted just to throw IOExceptions...
catch (Exception e) {
String detailMessage = e.getMessage();
if (null == detailMessage)
detailMessage = SQLServerException.getErrString("R_streamReadReturnedInvalidValue");
IOException ioException = new IOException(detailMessage);
ioException.initCause(e);
throw ioException;
}
if (charsRead < -1 || 0 == charsRead)
throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue"));
if (-1 == charsRead) {
// If the reader violates its interface contract then throw an exception.
if (rawChars.position() != lastPosition)
throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue"));
// Check that the reader has returned exactly the amount of data we expect
if (DataTypes.UNKNOWN_STREAM_LENGTH != readerLength && 0 != readerLength - readerCharsRead) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
throw new IOException(form.format(new Object[] {readerLength, readerCharsRead}));
}
// If there are no characters left to encode then we're done.
if (0 == rawChars.position()) {
rawChars = null;
atEndOfStream = true;
return false;
}
// Otherwise, we've filled the buffer as much as we can.
break;
}
assert charsRead > 0;
// If the reader violates its interface contract then throw an exception.
if (charsRead != rawChars.position() - lastPosition)
throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue"));
// Check that the reader isn't trying to return more data than we expect
if (DataTypes.UNKNOWN_STREAM_LENGTH != readerLength && charsRead > readerLength - readerCharsRead) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_mismatchedStreamLength"));
throw new IOException(form.format(new Object[] {readerLength, readerCharsRead}));
}
readerCharsRead += charsRead;
}
// The raw character buffer may now have characters available for encoding.
// Flip the buffer back to be ready for get (charset encode) operations.
rawChars.flip();
}
// If the raw character buffer remains empty, despite our efforts to (re)populate it,
// then no characters can be encoded at this time. This can happen if the reader reports
// that no characters were ready to be read.
if (!rawChars.hasRemaining())
return false;
// Raw characters are now available to be encoded, so encode them.
encodedChars = charset.encode(rawChars);
return true;
}
}