com.microsoft.sqlserver.jdbc.ReaderInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
//---------------------------------------------------------------------------------------------------------------------------------
// File: ReaderInputStream.java
//
//
// Microsoft JDBC Driver for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
package com.microsoft.sqlserver.jdbc;
import java.io.*;
import java.nio.charset.*;
import java.nio.*;
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, String charsetName, long readerLength) throws UnsupportedEncodingException
{
assert reader != null;
assert charsetName != null;
assert DataTypes.UNKNOWN_STREAM_LENGTH == readerLength || readerLength >= 0;
this.reader = reader;
try
{
this.charset = Charset.forName(charsetName);
}
catch (IllegalCharsetNameException e)
{
throw new UnsupportedEncodingException(e.getMessage());
}
catch (UnsupportedCharsetException e)
{
throw new UnsupportedEncodingException(e.getMessage());
}
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;
}
}