// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
package net.sourceforge.jtds.jdbc;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.io.UnsupportedEncodingException;
import net.sourceforge.jtds.jdbc.SharedSocket.VirtualSocket;
import net.sourceforge.jtds.util.*;
/**
* Implements an input stream for the server response.
* <p/>
* Implementation note:
* <ol>
* <li>This class contains methods to read different types of data from the
* server response stream in TDS format.
* <li>Character translation of String items is carried out.
* </ol>
*
* @author Mike Hutchinson.
* @version $Id: ResponseStream.java,v 1.20 2005-10-27 13:22:33 alin_sinpalean Exp $
*/
public class ResponseStream {
/** The shared network socket. */
private final SharedSocket socket;
/** The Input packet buffer. */
private byte[] buffer;
/** The offset of the next byte to read. */
private int bufferPtr;
/** The length of current input packet. */
private int bufferLen;
/**
* The {@link VirtualSocket} used by this stream.
*/
private final VirtualSocket _VirtualSocket;
/** True if stream is closed. */
private boolean isClosed;
/** A shared byte buffer. */
private final byte[] byteBuffer = new byte[255];
/** A shared char buffer. */
private final char[] charBuffer = new char[255];
/**
* Constructs a <code>RequestStream</code> object.
*
* @param socket
* the shared socket object to write to
*
* @param vsock
* the {@link VirtualSocket} used by this stream (from ResponseStream)
*
* @param bufferSize
* the initial buffer size
*/
ResponseStream( SharedSocket socket, VirtualSocket vsock, int bufferSize )
{
_VirtualSocket = vsock;
this.socket = socket;
buffer = new byte[bufferSize];
bufferLen = bufferSize;
bufferPtr = bufferSize;
}
/**
* Retrieves the {@link VirtualSocket} used by this stream id.
*
* @return the unique stream id as an <code>int</code>
*/
VirtualSocket getVirtualSocket()
{
return _VirtualSocket;
}
/**
* Retrieves the next input byte without reading forward.
*
* @return the next byte in the input stream as an <code>int</code>
* @throws IOException if an I/O error occurs
*/
int peek() throws IOException {
int b = read();
bufferPtr--; // Backup one
return b;
}
/**
* Reads the next input byte from the server response stream.
*
* @return the next byte in the input stream as an <code>int</code>
* @throws IOException if an I/O error occurs
*/
int read() throws IOException {
if (bufferPtr >= bufferLen) {
getPacket();
}
return buffer[bufferPtr++] & 0xFF;
}
/**
* Reads a byte array from the server response stream.
*
* @param b the byte array to read into
* @return the number of bytes read as an <code>int</code>
* @throws IOException if an I/O error occurs
*/
int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads a byte array from the server response stream, specifying a start
* offset and length.
*
* @param b the byte array
* @param off the starting offset in the array
* @param len the number of bytes to read
* @return the number of bytes read as an <code>int</code>
* @throws IOException if an I/O error occurs
*/
int read(byte[] b, int off, int len) throws IOException {
int bytesToRead = len;
while (bytesToRead > 0) {
if (bufferPtr >= bufferLen) {
getPacket();
}
int available = bufferLen - bufferPtr;
int bc = (available > bytesToRead) ? bytesToRead : available;
System.arraycopy(buffer, bufferPtr, b, off, bc);
off += bc;
bytesToRead -= bc;
bufferPtr += bc;
}
return len;
}
/**
* Reads a char array from the server response stream.
*
* @param c the char array
* @return the byte array as a <code>byte[]</code>
* @throws IOException if an I/O error occurs
*/
int read(char[] c) throws IOException {
for (int i = 0; i < c.length; i++) {
if (bufferPtr >= bufferLen) {
getPacket();
}
int b1 = buffer[bufferPtr++] & 0xFF;
if (bufferPtr >= bufferLen) {
getPacket();
}
int b2 = buffer[bufferPtr++] << 8;
c[i] = (char) (b2 | b1);
}
return c.length;
}
/**
* Reads a <code>String</code> object from the server response stream. If
* the TDS protocol version is 4.2 or 5.0 decode the string use the default
* server charset, otherwise use UCS2-LE (Unicode).
*
* @param len the length of the string to read <b>in bytes</b> in the case
* of TDS 4.2/5.0 and <b>in characters</b> for TDS 7.0+
* (UCS2-LE encoded strings)
* @return the result as a <code>String</code>
* @throws IOException if an I/O error occurs
*/
String readString(int len) throws IOException {
if (socket.getTdsVersion() >= Driver.TDS70) {
return readUnicodeString(len);
}
return readNonUnicodeString(len);
}
/**
* Skips a <code>String</code> from the server response stream. If the TDS
* protocol version is 4.2 or 5.0 <code>len</code> is the length in bytes,
* otherwise it's the length in UCS2-LE characters (length in bytes == 2 *
* <code>len</code>).
*
* @param len the length of the string to skip <b>in bytes</b> in the case
* of TDS 4.2/5.0 and <b>in characters</b> for TDS 7.0+
* (UCS2-LE encoded strings)
* @throws IOException if an I/O error occurs
*/
void skipString(int len) throws IOException {
if (len <= 0) {
return;
}
if (socket.getTdsVersion() >= Driver.TDS70) {
skip(len * 2);
} else {
skip(len);
}
}
/**
* Reads a UCS2-LE (Unicode) encoded String object from the server response
* stream.
*
* @param len the length of the string to read <b>in characters</b>
* @return the result as a <code>String</code>
* @throws IOException if an I/O error occurs
*/
String readUnicodeString(int len) throws IOException {
char[] chars = (len > charBuffer.length) ? new char[len] : charBuffer;
for (int i = 0; i < len; i++) {
if (bufferPtr >= bufferLen) {
getPacket();
}
int b1 = buffer[bufferPtr++] & 0xFF;
if (bufferPtr >= bufferLen) {
getPacket();
}
int b2 = buffer[bufferPtr++] << 8;
chars[i] = (char) (b2 | b1);
}
return new String(chars, 0, len);
}
/**
* Reads a non Unicode <code>String</code> from the server response stream,
* creating the <code>String</code> from a translated <code>byte</code>
* array.
*
* @param len the length of the string to read <b>in bytes</b>
* @return the result as a <code>String</code>
* @throws IOException if an I/O error occurs
*/
String readNonUnicodeString(int len) throws IOException {
CharsetInfo info = socket.getCharsetInfo();
return readString(len, info);
}
/**
* Reads a <code>String</code> from the server response stream, translating
* it from a <code>byte</code> array using the specified character set.
*
* @param len the length of the string to read <b>in bytes</b>
* @return the result as a <code>String</code>
* @throws IOException if an I/O error occurs
*/
String readNonUnicodeString(int len, CharsetInfo charsetInfo)
throws IOException {
return readString(len, charsetInfo);
}
/**
* Reads a <code>String</code> from the server response stream, creating
* it from a translated <code>byte</code> array.
*
* @param len the length of the string to read <b>in bytes</b>
* @param info descriptor of the charset to use
* @return the result as a <code>String</code>
* @throws IOException if an I/O error occurs
*/
String readString(int len, CharsetInfo info) throws IOException {
String charsetName = info.getCharset();
byte[] bytes = (len > byteBuffer.length) ? new byte[len] : byteBuffer;
read(bytes, 0, len);
try {
return new String(bytes, 0, len, charsetName);
} catch (UnsupportedEncodingException e) {
return new String(bytes, 0, len);
}
}
/**
* Reads a <code>short</code> value from the server response stream.
*
* @return the result as a <code>short</code>
* @throws IOException if an I/O error occurs
*/
short readShort() throws IOException {
int b1 = read();
return (short) (b1 | (read() << 8));
}
/**
* Reads an <code>int</code> value from the server response stream.
*
* @return the result as a <code>int</code>
* @throws IOException if an I/O error occurs
*/
int readInt() throws IOException {
int b1 = read();
int b2 = read() << 8;
int b3 = read() << 16;
int b4 = read() << 24;
return b4 | b3 | b2 | b1;
}
/**
* Reads a <code>long</code> value from the server response stream.
*
* @return the result as a <code>long</code>
* @throws IOException if an I/O error occurs
*/
long readLong() throws IOException {
long b1 = read();
long b2 = ((long) read()) << 8;
long b3 = ((long) read()) << 16;
long b4 = ((long) read()) << 24;
long b5 = ((long) read()) << 32;
long b6 = ((long) read()) << 40;
long b7 = ((long) read()) << 48;
long b8 = ((long) read()) << 56;
return b1 | b2 | b3 | b4 | b5 | b6 | b7 | b8;
}
/**
* Reads an <code>unsigned long</code> value from the server response stream.
*
* @return the result as a <code>BigDecimal</code>
* @throws IOException if an I/O error occurs
*/
BigDecimal readUnsignedLong() throws IOException {
int b1 = (read() & 0xFF);
long b2 = read();
long b3 = ((long) read()) << 8;
long b4 = ((long) read()) << 16;
long b5 = ((long) read()) << 24;
long b6 = ((long) read()) << 32;
long b7 = ((long) read()) << 40;
long b8 = ((long) read()) << 48;
// Convert via String as BigDecimal(long) is actually BigDecimal(double)
// on older versions of java
return new BigDecimal(Long.toString(b2 | b3 | b4 | b5 | b6 | b7 | b8))
.multiply(new BigDecimal(256))
.add(new BigDecimal(b1));
}
/**
* Discards bytes from the server response stream.
*
* @param skip the number of bytes to discard
* @return the number of bytes skipped
*/
int skip(int skip) throws IOException {
int tmp = skip;
while (skip > 0) {
if (bufferPtr >= bufferLen) {
getPacket();
}
int available = bufferLen - bufferPtr;
if (skip > available) {
skip -= available;
bufferPtr = bufferLen;
} else {
bufferPtr += skip;
skip = 0;
}
}
return tmp;
}
/**
* Consumes the rest of the server response, without parsing it.
* <p/>
* <b>Note:</b> Use only in extreme cases, packets will not be parsed and
* could leave the connection in an inconsistent state.
*/
void skipToEnd() {
try {
// No more data to read.
bufferPtr = bufferLen;
// Now consume all data until we get an exception.
while (true) {
buffer = socket.getNetPacket(_VirtualSocket, buffer);
}
} catch (IOException ex) {
// Ignore it. Probably no more packets.
}
}
/**
* Closes this response stream. The stream id is unlinked from the
* underlying shared socket as well.
*/
void close() {
isClosed = true;
socket.closeStream(_VirtualSocket);
}
/**
* Retrieves the TDS version number.
*
* @return the TDS version as an <code>int</code>
*/
int getTdsVersion() {
return socket.getTdsVersion();
}
/**
* Retrieves the server type.
*
* @return the server type as an <code>int</code>
*/
int getServerType() {
return socket.serverType;
}
/**
* Creates a simple <code>InputStream</code> over the server response.
* <p/>
* This method can be used to obtain a stream which can be passed to
* <code>InputStreamReader</code>s to assist in reading multi byte
* character sets.
*
* @param len the number of bytes available in the server response
* @return the <code>InputStream</code> built over the server response
*/
InputStream getInputStream(int len) {
return new TdsInputStream(this, len);
}
/**
* Read the next TDS packet from the network.
*
* @throws IOException if an I/O error occurs
*/
private void getPacket() throws IOException {
while (bufferPtr >= bufferLen) {
if (isClosed) {
throw new IOException("ResponseStream is closed");
}
buffer = socket.getNetPacket(_VirtualSocket, buffer);
bufferLen = ((buffer[2] & 0xFF) << 8) | (buffer[3] & 0xFF);
bufferPtr = TdsCore.PKT_HDR_LEN;
if (Logger.isActive()) {
Logger.logPacket(_VirtualSocket.id, true, buffer);
}
}
}
/**
* Simple inner class implementing an <code>InputStream</code> over the
* server response.
*/
private static class TdsInputStream extends InputStream {
/** The underlying <code>ResponseStream</code>. */
ResponseStream tds;
/** The maximum amount of data to make available. */
int maxLen;
/**
* Creates a <code>TdsInputStream</code> instance.
*
* @param tds the underlying <code>ResponseStream</code>
* @param maxLen the maximum amount of data that will be available
*/
public TdsInputStream(ResponseStream tds, int maxLen) {
this.tds = tds;
this.maxLen = maxLen;
}
public int read() throws IOException {
return (maxLen-- > 0)? tds.read(): -1;
}
public int read(byte[] bytes, int offset, int len) throws IOException {
if (maxLen < 1) {
return -1;
} else {
int bc = Math.min(maxLen, len);
if (bc > 0) {
bc = tds.read(bytes, offset, bc);
maxLen -= (bc == -1) ? 0 : bc;
}
return bc;
}
}
}
}