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 4.2 Driver for SQL Server.
The Azure Key Vault feature in Microsoft JDBC Driver for SQL Server depends on
Azure SDK for JAVA and Azure Active Directory Library For Java.
//---------------------------------------------------------------------------------------------------------------------------------
// 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;
}
}