All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.ReaderInputStream Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
//---------------------------------------------------------------------------------------------------------------------------------
// 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy