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

org.firebirdsql.gds.impl.wire.XdrOutputStream Maven / Gradle / Ivy

There is a newer version: 2.2.7
Show newest version
/*
 * Firebird Open Source J2ee connector - jdbc driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program 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
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a CVS history command.
 *
 * All rights reserved.
 */

/*
 * The Original Code is the Firebird Java GDS implementation.
 *
 * The Initial Developer of the Original Code is Alejandro Alberola.
 * Portions created by Alejandro Alberola are Copyright (C) 2001
 * Boix i Oltra, S.L. All Rights Reserved.
 */

package org.firebirdsql.gds.impl.wire;


import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;

import org.firebirdsql.gds.*;

import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;

/**
 * An XdrOutputStream writes data in XDR format to an 
 * underlying java.io.OutputStream.
 *
 * @author Alejandro Alberola
 * @author David Jencks
 * @version 1.0
 */
public class XdrOutputStream {

    private static final int BUF_SIZE = 32767;

    private static final Logger log = LoggerFactory.getLogger(XdrOutputStream.class,false);
    private static final byte[] textPad = new byte[BUF_SIZE];
    private static final byte[] zero = new XSQLVAR().encodeInt(0);   // todo
    private static final byte[] minusOne = new XSQLVAR().encodeInt(-1);
    
    static {
        // fill the padding with blanks
        Arrays.fill(textPad,(byte) 32);
    }

    private byte[] buf = new byte[BUF_SIZE];
    private int count;
    private OutputStream out;

    protected XdrOutputStream() {
        // empty, for subclasses.
    }
    
    /**
     * Create a new instance of XdrOutputStream.
     *
     * @param out The underlying OutputStream to write to
     */
    public XdrOutputStream(OutputStream out) {
        this.out = out;
    }

    /**
     * Write a byte buffer to the underlying output stream in
     * XDR format.
     *
     * @param buffer The byte buffer to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeBuffer(byte[] buffer) throws IOException {
        if (buffer == null)
            writeInt(0);
        else {
            int len = buffer.length;
            writeInt(len);
            write(buffer, len, (4 - len) & 3);
        }
    }

    /**
     * Write a blob buffer to the underlying output stream in XDR format.
     *
     * @param buffer A byte array containing the blob
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeBlobBuffer(byte[] buffer) throws IOException {
        int len = buffer.length ; // 2 for short for buffer length
        if (log != null && log.isDebugEnabled()) log.debug("writeBlobBuffer len: " + len);
        if (len > Short.MAX_VALUE) {
            throw new IOException(""); //Need a value???
        }
        writeInt(len + 2);
        writeInt(len + 2); //bizarre but true! three copies of the length
        checkBufferSize(2);
        buf[count++] = (byte) ((len >> 0) & 0xff);
        buf[count++] = (byte) ((len >> 8) & 0xff);
        write(buffer, len, (4 - len + 2) & 3); // TODO Is this correct, shouldn't it be (4 - (len + 2)) & 3; see writeSet
    }

    /**
     * Write a String to the underlying output stream in
     * XDR format.
     *
     * @param s The String to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeString(String s) throws IOException {
        byte[] buffer = s.getBytes();
        writeBuffer(buffer);
    }

    /**
     * Write content of the specified string using the specified Java encoding.
     */
    public void writeString(String s, String encoding) throws IOException {
        if (encoding != null)
            writeBuffer(s.getBytes(encoding));
        else
            writeBuffer(s.getBytes());
    }

    /**
     * Write a set of distinct byte values (ie parameter buffer).
     *
     * @param type The type of the parameter value being written, 
     *        e.g. {@link ISCConstants#isc_tpb_version3}
     * @param s The set of byte values to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeSet(int type, byte[] s) throws IOException {
        if (s == null) {
            writeInt(1);
            write(type); //e.g. gds.isc_tpb_version3
        } else {
            int len = s.length;
            writeInt(len + 1);
            write(type);
            write(s, len, (4 - ( len + 1 )) & 3); // TODO Is this correct; see writeBlobBuffer, that suggests it should be (4 - len + 1) & 3
        }
    }

    /**
     * Write an Xdrable to this output stream.
     *
     * @param type Type of the Xdrable to be written, 
     *        e.g. {@link ISCConstants#isc_tpb_version3}
     * @param item The object to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeTyped(int type, Xdrable item) throws IOException {
        int size;
        if (item == null) {
            writeInt(1);
            write(type); //e.g. gds.isc_tpb_version3
            size = 1;
        } else {
            size = item.getLength() + 1;
            writeInt(size);
            write(type);
            item.write(this);
        }
        // TODO Find out if we need to pad with 0x00 instead
        write(textPad, (4 - size) & 3, 0);
    }

    // 
    // WriteSQLData methods
    // 

    /**
     * Write a set of SQL data from a XSQLDA data structure.
     *
     * @param xsqlda The datastructure containing the SQL data to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeSQLData(XSQLDA xsqlda) throws IOException {
        for (int i = 0; i < xsqlda.sqld; i++) {
            XSQLVAR xsqlvar = xsqlda.sqlvar[i];
            if (log != null && log.isDebugEnabled()) {
                if (out == null) {
                    log.debug("db.out null in writeSQLDatum");
                }
                if (xsqlvar.sqldata == null) {
                    log.debug("sqldata null in writeSQLDatum: " + xsqlvar);
                }
                if (xsqlvar.sqldata == null) {
                    log.debug("sqldata still null in writeSQLDatum: " + xsqlvar);
                }
            }
            int len = xsqlda.ioLength[i];
            byte[] buffer = xsqlvar.sqldata;
            int tempType = xsqlvar.sqltype & ~1;
            if (tempType == ISCConstants.SQL_NULL) {
                write(xsqlvar.sqldata != null ? zero : minusOne, 4, 0);
            } else
            if (len==0) {
                if (buffer != null) {
                    len = buffer.length;
                    writeInt(len);
                    write(buffer, len, (4 - len) & 3);
                    // sqlind
                    write(zero, 4, 0);
                }
                else{
                    writeInt(0);
                    // sqlind
                    write(minusOne, 4, 0);
                }
            }
            else if (len < 0){
                if (buffer != null) {
                    write(buffer, -len, 0);
                    // sqlind
                    write(zero,4, 0);
                }
                else{
                    write(textPad, -len, 0);
                    // sqlind
                    write(minusOne, 4, 0);
                }
            }
            else {
                // decrement length because it was incremented before
                len--;
                if (buffer != null) {
                    int buflen = buffer.length;
                    if (buflen >=len){
                        write(buffer, len, (4 - len) & 3);
                    }
                    else{
                        write(buffer, buflen, 0);
                        write(textPad, len- buflen, (4 - len) & 3);
                    }
                    // sqlind
                    write(zero, 4, 0);
                }
                else{
                    write(textPad, len, (4 - len) & 3);
                    // sqlind
                    write(minusOne, 4, 0);
                }
            }
        }
    }

    //
    // DataOutputStream methods
    // 
    
    /**
     * Write a long value to the underlying stream in XDR format.
     *
     * @param v The long value to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeLong(long v) throws IOException {
        checkBufferSize(8);
        buf[count++] = (byte) (v >>> 56 & 0xFF);
        buf[count++] = (byte) (v >>> 48 & 0xFF);
        buf[count++] = (byte) (v >>> 40 & 0xFF);
        buf[count++] = (byte) (v >>> 32 & 0xFF);
        buf[count++] = (byte) (v >>> 24 & 0xFF);
        buf[count++] = (byte) (v >>> 16 & 0xFF);
        buf[count++] = (byte) (v >>>  8 & 0xFF);
        buf[count++] = (byte) (v >>>  0 & 0xFF);
    }

    /**
     * Write an int value to the underlying stream in XDR format.
     *
     * @param v The int value to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void writeInt(int v) throws IOException {
        checkBufferSize(4);
        buf[count++] = (byte) (v >>> 24);
        buf[count++] = (byte) (v >>> 16);
        buf[count++] = (byte) (v >>>  8);
        buf[count++] = (byte) (v >>>  0);
    }

    //
    // Buffering 
    // If the piece to write is greater than 256 bytes, write it directly
    //

    /**
     * Write a byte buffer to the underlying output stream
     * in XDR format
     *
     * @param b The byte buffer to be written 
     * @param len The number of bytes to write 
     * @param pad The number of (blank) padding bytes to write
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void write(byte[] b, int len, int pad) throws IOException {
        if (len > 256 || count + len + pad >= BUF_SIZE){
            if (count > 0)
                out.write(buf, 0, count);
            out.write(b, 0, len);
            out.write(textPad, 0, pad);
            count = 0;
        } else {
            checkBufferSize(len + pad);
            if (len > 0) {
                System.arraycopy(b, 0, buf, count, len);
                count += len;
            }
            if (pad > 0) {
                System.arraycopy(textPad, 0, buf, count, pad);
                count += pad;
            }
        }
    }

    /**
     * Write a single byte to the underlying output stream in 
     * XDR format.
     *
     * @param b The value to be written, will be truncated to a byte
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void write(int b) throws IOException {
        checkBufferSize(1);
        buf[count++] = (byte)b;
    }

    /**
     * Write an array of bytes to the underlying output stream
     * in XDR format.
     *
     * @param b The byte array to be written
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void write(byte b[]) throws IOException{
        write(b, b.length, 0);
    }

    /**
     * Flush all buffered data to the underlying output stream.
     * 
     * @throws IOException if an error occurs while writing to the 
     *         underlying output stream
     */
    public void flush() throws IOException {
        if (count > 0){
            out.write(buf, 0, count);
        }
        count = 0;
        out.flush();
    }

    /**
     * Close this stream and the underlying output stream.
     *
     * @throws IOException if an error occurs while closing the 
     *         underlying stream
     */
    public void close() throws IOException {
        if (out == null) {
            return;
        }
        IOException firstException = null;
        
        try {
            out.flush();
        } catch (IOException ex) {
            firstException = ex;
        } catch (RuntimeException ex) {
            firstException = new IOException("Runtime exception flushing XdrOutputStream during close()");
            firstException.initCause(ex);
        }
        
        try {
            out.close();
        } catch (IOException ex) {
            if (firstException == null) {
                firstException = ex;
            } else if (log != null) {
                log.debug("Ignoring IOException closing XdrOutputStream; received an earlier exception, ignored exception:", ex);
            }
        } finally {
            buf = null;
            out = null;
        }
        
        if (firstException != null) {
            throw firstException;
        }
    }

    /**
     * Ensure that there is room in the buffer for a given number
     * of direct writes to it.
     * @param countToWrite The size of write that is being checked.
     * @throws IOException If a write to the underlying OutputStream fails
     */
    private void checkBufferSize(int countToWrite) throws IOException {
        if (BUF_SIZE - count <= countToWrite){
            out.write(buf, 0, count);
            count = 0;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy