/*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.sql.rowset.serial;
import java.sql.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.Arrays;
/**
* A serialized mapping in the Java programming language of an SQL
* BLOB
value.
*
* The SerialBlob
class provides a constructor for creating
* an instance from a Blob
object. Note that the
* Blob
* object should have brought the SQL BLOB
value's data over
* to the client before a SerialBlob
object
* is constructed from it. The data of an SQL BLOB
value can
* be materialized on the client as an array of bytes (using the method
* Blob.getBytes
) or as a stream of uninterpreted bytes
* (using the method Blob.getBinaryStream
).
*
* SerialBlob
methods make it possible to make a copy of a
* SerialBlob
object as an array of bytes or as a stream.
* They also make it possible to locate a given pattern of bytes or a
* Blob
object within a SerialBlob
object
* and to update or truncate a Blob
object.
*
*
A SerialBlob is not safe for use by multiple concurrent threads. If a
* SerialBlob is to be used by more than one thread then access to the SerialBlob
* should be controlled by appropriate synchronization.
*
* @author Jonathan Bruce
*/
public class SerialBlob implements Blob, Serializable, Cloneable {
/**
* A serialized array of uninterpreted bytes representing the
* value of this SerialBlob
object.
* @serial
*/
private byte buf[];
/**
* The internal representation of the Blob
object on which this
* SerialBlob
object is based.
*/
private Blob blob;
/**
* The number of bytes in this SerialBlob
object's
* array of bytes.
* @serial
*/
private long len;
/**
* The original number of bytes in this SerialBlob
object's
* array of bytes when it was first established.
* @serial
*/
private long origLen;
/**
* Constructs a SerialBlob
object that is a serialized version of
* the given byte
array.
*
* The new SerialBlob
object is initialized with the data from the
* byte
array, thus allowing disconnected RowSet
* objects to establish serialized Blob
objects without
* touching the data source.
*
* @param b the byte
array containing the data for the
* Blob
object to be serialized
* @throws SerialException if an error occurs during serialization
* @throws SQLException if a SQL errors occurs
*/
public SerialBlob(byte[] b) throws SerialException, SQLException {
len = b.length;
buf = new byte[(int)len];
for(int i = 0; i < len; i++) {
buf[i] = b[i];
}
origLen = len;
}
/**
* Constructs a SerialBlob
object that is a serialized
* version of the given Blob
object.
*
* The new SerialBlob
object is initialized with the
* data from the Blob
object; therefore, the
* Blob
object should have previously brought the
* SQL BLOB
value's data over to the client from
* the database. Otherwise, the new SerialBlob
object
* will contain no data.
*
* @param blob the Blob
object from which this
* SerialBlob
object is to be constructed;
* cannot be null.
* @throws SerialException if an error occurs during serialization
* @throws SQLException if the Blob
passed to this
* to this constructor is a null
.
* @see java.sql.Blob
*/
public SerialBlob (Blob blob) throws SerialException, SQLException {
if (blob == null) {
throw new SQLException("Cannot instantiate a SerialBlob " +
"object with a null Blob object");
}
len = blob.length();
buf = blob.getBytes(1, (int)len );
this.blob = blob;
//if ( len < 10240000)
// len = 10240000;
origLen = len;
}
/**
* Copies the specified number of bytes, starting at the given
* position, from this SerialBlob
object to
* another array of bytes.
*
* Note that if the given number of bytes to be copied is larger than
* the length of this SerialBlob
object's array of
* bytes, the given number will be shortened to the array's length.
*
* @param pos the ordinal position of the first byte in this
* SerialBlob
object to be copied;
* numbering starts at 1
; must not be less
* than 1
and must be less than or equal
* to the length of this SerialBlob
object
* @param length the number of bytes to be copied
* @return an array of bytes that is a copy of a region of this
* SerialBlob
object, starting at the given
* position and containing the given number of consecutive bytes
* @throws SerialException if the given starting position is out of bounds;
* if {@code free} had previously been called on this object
*/
public byte[] getBytes(long pos, int length) throws SerialException {
isValid();
if (length > len) {
length = (int)len;
}
if (pos < 1 || len - pos < 0 ) {
throw new SerialException("Invalid arguments: position cannot be "
+ "less than 1 or greater than the length of the SerialBlob");
}
pos--; // correct pos to array index
byte[] b = new byte[length];
for (int i = 0; i < length; i++) {
b[i] = this.buf[(int)pos];
pos++;
}
return b;
}
/**
* Retrieves the number of bytes in this SerialBlob
* object's array of bytes.
*
* @return a long
indicating the length in bytes of this
* SerialBlob
object's array of bytes
* @throws SerialException if an error occurs;
* if {@code free} had previously been called on this object
*/
public long length() throws SerialException {
isValid();
return len;
}
/**
* Returns this SerialBlob
object as an input stream.
* Unlike the related method, setBinaryStream
,
* a stream is produced regardless of whether the SerialBlob
* was created with a Blob
object or a byte
array.
*
* @return a java.io.InputStream
object that contains
* this SerialBlob
object's array of bytes
* @throws SerialException if an error occurs;
* if {@code free} had previously been called on this object
* @see #setBinaryStream
*/
public java.io.InputStream getBinaryStream() throws SerialException {
isValid();
InputStream stream = new ByteArrayInputStream(buf);
return stream;
}
/**
* Returns the position in this SerialBlob
object where
* the given pattern of bytes begins, starting the search at the
* specified position.
*
* @param pattern the pattern of bytes for which to search
* @param start the position of the byte in this
* SerialBlob
object from which to begin
* the search; the first position is 1
;
* must not be less than 1
nor greater than
* the length of this SerialBlob
object
* @return the position in this SerialBlob
object
* where the given pattern begins, starting at the specified
* position; -1
if the pattern is not found
* or the given starting position is out of bounds; position
* numbering for the return value starts at 1
* @throws SerialException if an error occurs when serializing the blob;
* if {@code free} had previously been called on this object
* @throws SQLException if there is an error accessing the BLOB
* value from the database
*/
public long position(byte[] pattern, long start)
throws SerialException, SQLException {
isValid();
if (start < 1 || start > len) {
return -1;
}
int pos = (int)start-1; // internally Blobs are stored as arrays.
int i = 0;
long patlen = pattern.length;
while (pos < len) {
if (pattern[i] == buf[pos]) {
if (i + 1 == patlen) {
return (pos + 1) - (patlen - 1);
}
i++; pos++; // increment pos, and i
} else if (pattern[i] != buf[pos]) {
pos++; // increment pos only
}
}
return -1; // not found
}
/**
* Returns the position in this SerialBlob
object where
* the given Blob
object begins, starting the search at the
* specified position.
*
* @param pattern the Blob
object for which to search;
* @param start the position of the byte in this
* SerialBlob
object from which to begin
* the search; the first position is 1
;
* must not be less than 1
nor greater than
* the length of this SerialBlob
object
* @return the position in this SerialBlob
object
* where the given Blob
object begins, starting
* at the specified position; -1
if the pattern is
* not found or the given starting position is out of bounds;
* position numbering for the return value starts at 1
* @throws SerialException if an error occurs when serializing the blob;
* if {@code free} had previously been called on this object
* @throws SQLException if there is an error accessing the BLOB
* value from the database
*/
public long position(Blob pattern, long start)
throws SerialException, SQLException {
isValid();
return position(pattern.getBytes(1, (int)(pattern.length())), start);
}
/**
* Writes the given array of bytes to the BLOB
value that
* this Blob
object represents, starting at position
* pos
, and returns the number of bytes written.
*
* @param pos the position in the SQL BLOB
value at which
* to start writing. The first position is 1
;
* must not be less than 1
nor greater than
* the length of this SerialBlob
object.
* @param bytes the array of bytes to be written to the BLOB
* value that this Blob
object represents
* @return the number of bytes written
* @throws SerialException if there is an error accessing the
* BLOB
value; or if an invalid position is set; if an
* invalid offset value is set;
* if {@code free} had previously been called on this object
* @throws SQLException if there is an error accessing the BLOB
* value from the database
* @see #getBytes
*/
public int setBytes(long pos, byte[] bytes)
throws SerialException, SQLException {
return (setBytes(pos, bytes, 0, bytes.length));
}
/**
* Writes all or part of the given byte
array to the
* BLOB
value that this Blob
object represents
* and returns the number of bytes written.
* Writing starts at position pos
in the BLOB
* value; len bytes from the given byte array are written.
*
* @param pos the position in the BLOB
object at which
* to start writing. The first position is 1
;
* must not be less than 1
nor greater than
* the length of this SerialBlob
object.
* @param bytes the array of bytes to be written to the BLOB
* value
* @param offset the offset in the byte
array at which
* to start reading the bytes. The first offset position is
* 0
; must not be less than 0
nor greater
* than the length of the byte
array
* @param length the number of bytes to be written to the
* BLOB
value from the array of bytes bytes.
*
* @return the number of bytes written
* @throws SerialException if there is an error accessing the
* BLOB
value; if an invalid position is set; if an
* invalid offset value is set; if number of bytes to be written
* is greater than the SerialBlob
length; or the combined
* values of the length and offset is greater than the Blob buffer;
* if {@code free} had previously been called on this object
* @throws SQLException if there is an error accessing the BLOB
* value from the database.
* @see #getBytes
*/
public int setBytes(long pos, byte[] bytes, int offset, int length)
throws SerialException, SQLException {
isValid();
if (offset < 0 || offset > bytes.length) {
throw new SerialException("Invalid offset in byte array set");
}
if (pos < 1 || pos > this.length()) {
throw new SerialException("Invalid position in BLOB object set");
}
if ((long)(length) > origLen) {
throw new SerialException("Buffer is not sufficient to hold the value");
}
if ((length + offset) > bytes.length) {
throw new SerialException("Invalid OffSet. Cannot have combined offset " +
"and length that is greater that the Blob buffer");
}
int i = 0;
pos--; // correct to array indexing
while ( i < length || (offset + i +1) < (bytes.length-offset) ) {
this.buf[(int)pos + i] = bytes[offset + i ];
i++;
}
return i;
}
/**
* Retrieves a stream that can be used to write to the BLOB
* value that this Blob
object represents. The stream begins
* at position pos
. This method forwards the
* setBinaryStream()
call to the underlying Blob
in
* the event that this SerialBlob
object is instantiated with a
* Blob
. If this SerialBlob
is instantiated with
* a byte
array, a SerialException
is thrown.
*
* @param pos the position in the BLOB
value at which
* to start writing
* @return a java.io.OutputStream
object to which data can
* be written
* @throws SQLException if there is an error accessing the
* BLOB
value
* @throws SerialException if the SerialBlob in not instantiated with a
* Blob
object that supports setBinaryStream()
;
* if {@code free} had previously been called on this object
* @see #getBinaryStream
*/
public java.io.OutputStream setBinaryStream(long pos)
throws SerialException, SQLException {
isValid();
if (this.blob != null) {
return this.blob.setBinaryStream(pos);
} else {
throw new SerialException("Unsupported operation. SerialBlob cannot " +
"return a writable binary stream, unless instantiated with a Blob object " +
"that provides a setBinaryStream() implementation");
}
}
/**
* Truncates the BLOB
value that this Blob
* object represents to be len
bytes in length.
*
* @param length the length, in bytes, to which the BLOB
* value that this Blob
object represents should be
* truncated
* @throws SerialException if there is an error accessing the Blob value;
* or the length to truncate is greater that the SerialBlob length;
* if {@code free} had previously been called on this object
*/
public void truncate(long length) throws SerialException {
isValid();
if (length > len) {
throw new SerialException
("Length more than what can be truncated");
} else if((int)length == 0) {
buf = new byte[0];
len = length;
} else {
len = length;
buf = this.getBytes(1, (int)len);
}
}
/**
* Returns an
* InputStream
object that contains a partial
* {@code Blob} value, starting with the byte specified by pos, which is
* length bytes in length.
*
* @param pos the offset to the first byte of the partial value to be
* retrieved. The first byte in the {@code Blob} is at position 1
* @param length the length in bytes of the partial value to be retrieved
* @return
* InputStream
through which the partial {@code Blob} value can
* be read.
* @throws SQLException if pos is less than 1 or if pos is greater than the
* number of bytes in the {@code Blob} or if pos + length is greater than
* the number of bytes in the {@code Blob}
* @throws SerialException if the {@code free} method had been previously
* called on this object
*
* @since 1.6
*/
public InputStream getBinaryStream(long pos, long length) throws SQLException {
isValid();
if (pos < 1 || pos > this.length()) {
throw new SerialException("Invalid position in BLOB object set");
}
if (length < 1 || length > len - pos + 1) {
throw new SerialException("length is < 1 or pos + length >"
+ "total number of bytes");
}
return new ByteArrayInputStream(buf, (int) pos - 1, (int) length);
}
/**
* This method frees the {@code SeriableBlob} object and releases the
* resources that it holds. The object is invalid once the {@code free}
* method is called.
If {@code free} is called multiple times, the * subsequent calls to {@code free} are treated as a no-op.
* * @throws SQLException if an error occurs releasing the Blob's resources * @since 1.6 */ public void free() throws SQLException { if (buf != null) { buf = null; if (blob != null) { blob.free(); } blob = null; } } /** * Compares this SerialBlob to the specified object. The result is {@code * true} if and only if the argument is not {@code null} and is a {@code * SerialBlob} object that represents the same sequence of bytes as this * object. * * @param obj The object to compare this {@code SerialBlob} against * * @return {@code true} if the given object represents a {@code SerialBlob} * equivalent to this SerialBlob, {@code false} otherwise * */ public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof SerialBlob) { SerialBlob sb = (SerialBlob)obj; if (this.len == sb.len) { return Arrays.equals(buf, sb.buf); } } return false; } /** * Returns a hash code for this {@code SerialBlob}. * @return a hash code value for this object. */ public int hashCode() { return ((31 + Arrays.hashCode(buf)) * 31 + (int)len) * 31 + (int)origLen; } /** * Returns a clone of this {@code SerialBlob}. The copy will contain a * reference to a clone of the internal byte array, not a reference * to the original internal byte array of this {@code SerialBlob} object. * The underlying {@code Blob} object will be set to null. * * @return a clone of this SerialBlob */ public Object clone() { try { SerialBlob sb = (SerialBlob) super.clone(); sb.buf = (buf != null) ? Arrays.copyOf(buf, (int)len) : null; sb.blob = null; return sb; } catch (CloneNotSupportedException ex) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } /** * readObject is called to restore the state of the SerialBlob from * a stream. */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { ObjectInputStream.GetField fields = s.readFields(); byte[] tmp = (byte[])fields.get("buf", null); if (tmp == null) throw new InvalidObjectException("buf is null and should not be!"); buf = tmp.clone(); len = fields.get("len", 0L); if (buf.length != len) throw new InvalidObjectException("buf is not the expected size"); origLen = fields.get("origLen", 0L); blob = (Blob) fields.get("blob", null); } /** * writeObject is called to save the state of the SerialBlob * to a stream. */ private void writeObject(ObjectOutputStream s) throws IOException, ClassNotFoundException { ObjectOutputStream.PutField fields = s.putFields(); fields.put("buf", buf); fields.put("len", len); fields.put("origLen", origLen); // Note: this check to see if it is an instance of Serializable // is for backwards compatibiity fields.put("blob", blob instanceof Serializable ? blob : null); s.writeFields(); } /** * Check to see if this object had previously had its {@code free} method * called * * @throws SerialException */ private void isValid() throws SerialException { if (buf == null) { throw new SerialException("Error: You cannot call a method on a " + "SerialBlob instance once free() has been called."); } } /** * The identifier that assists in the serialization of this * {@code SerialBlob} object. */ static final long serialVersionUID = -8144641928112860441L; }