blob: f8c38ea46a475cf8fdc96a4e309bf1ab3e45f54b [file] [log] [blame]
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.SQLException;
import org.apache.cayenne.CayenneRuntimeException;
/**
* A Blob implementation that stores content in memory.
* <p>
* <i>This implementation is based on jdbcBlob from HSQLDB (copyright HSQL Development
* Group).</i>
* </p>
*
* @since 1.2
*/
public class MemoryBlob implements Blob {
volatile byte[] data;
public MemoryBlob() {
this(new byte[0]);
}
/**
* Constructs a new MemoryBlob instance wrapping the given octet sequence.
*
* @param data the octet sequence representing the Blob value
* @throws CayenneRuntimeException if the argument is null
*/
public MemoryBlob(byte[] data) {
if (data == null) {
throw new CayenneRuntimeException("Null data");
}
this.data = data;
}
/**
* Returns the number of bytes in the <code>BLOB</code> value designated by this
* <code>Blob</code> object.
*
* @return length of the <code>BLOB</code> in bytes
* @exception SQLException if there is an error accessing the length of the
* <code>BLOB</code>
*/
public long length() throws SQLException {
return data.length;
}
/**
* Retrieves all or part of the <code>BLOB</code> value that this <code>Blob</code>
* object represents, as an array of bytes. This <code>byte</code> array contains up
* to <code>length</code> consecutive bytes starting at position <code>pos</code>.
* <p>
* The official specification is ambiguous in that it does not precisely indicate the
* policy to be observed when pos > this.length() - length. One policy would be to
* retrieve the octets from pos to this.length(). Another would be to throw an
* exception. This implementation observes the later policy.
*
* @param pos the ordinal position of the first byte in the <code>BLOB</code> value to
* be extracted; the first byte is at position 1
* @param length the number of consecutive bytes to be copied
* @return a byte array containing up to <code>length</code> consecutive bytes from
* the <code>BLOB</code> value designated by this <code>Blob</code> object,
* starting with the byte at position <code>pos</code>
* @exception SQLException if there is an error accessing the <code>BLOB</code> value
*/
public byte[] getBytes(long pos, final int length) throws SQLException {
final byte[] ldata = data;
final int dlen = ldata.length;
pos--;
if (pos < 0 || pos > dlen) {
throw new SQLException("Invalid pos: " + (pos + 1));
}
if (length < 0 || length > dlen - pos) {
throw new SQLException("length: " + length);
}
final byte[] out = new byte[length];
System.arraycopy(ldata, (int) pos, out, 0, length);
return out;
}
/**
* Retrieves the <code>BLOB</code> value designated by this <code>Blob</code> instance
* as a stream.
*
* @return a stream containing the <code>BLOB</code> data
* @exception SQLException if there is an error accessing the <code>BLOB</code> value
*/
public InputStream getBinaryStream() throws SQLException {
return new ByteArrayInputStream(data);
}
/**
* Retrieves the byte position at which the specified byte array <code>pattern</code>
* begins within the <code>BLOB</code> value that this <code>Blob</code> object
* represents. The search for <code>pattern</code> begins at position
* <code>start</code>.
* <p>
*
* @param pattern the byte array for which to search
* @param start the position at which to begin searching; the first position is 1
* @return the position at which the pattern appears, else -1
* @exception SQLException if there is an error accessing the <code>BLOB</code>
*/
public long position(final byte[] pattern, long start) throws SQLException {
final byte[] ldata = data;
final int dlen = ldata.length;
if (start > dlen || pattern == null) {
return -1;
}
else if (start < 1) {
start = 0;
}
else {
start--;
}
final int plen = pattern.length;
if (plen == 0 || start > dlen - plen) {
return -1;
}
final int stop = dlen - plen;
final byte b0 = pattern[0];
outer_loop: for (int i = (int) start; i <= stop; i++) {
if (ldata[i] != b0) {
continue;
}
int len = plen;
int doffset = i;
int poffset = 0;
while (len-- > 0) {
if (ldata[doffset++] != pattern[poffset++]) {
continue outer_loop;
}
}
return i + 1;
}
return -1;
}
/**
* Retrieves the byte position in the <code>BLOB</code> value designated by this
* <code>Blob</code> object at which <code>pattern</code> begins. The search begins at
* position <code>start</code>.
*
* @param pattern the <code>Blob</code> object designating the <code>BLOB</code> value
* for which to search
* @param start the position in the <code>BLOB</code> value at which to begin
* searching; the first position is 1
* @return the position at which the pattern begins, else -1
* @exception SQLException if there is an error accessing the <code>BLOB</code> value
*/
public long position(final Blob pattern, long start) throws SQLException {
final byte[] ldata = data;
final int dlen = ldata.length;
if (start > dlen || pattern == null) {
return -1;
}
else if (start < 1) {
start = 0;
}
else {
start--;
}
final long plen = pattern.length();
if (plen == 0 || start > dlen - plen) {
return -1;
}
// by now, we know plen <= Integer.MAX_VALUE
final int iplen = (int) plen;
byte[] bap;
if (pattern instanceof MemoryBlob) {
bap = ((MemoryBlob) pattern).data;
}
else {
bap = pattern.getBytes(1, iplen);
}
final int stop = dlen - iplen;
final byte b0 = bap[0];
outer_loop: for (int i = (int) start; i <= stop; i++) {
if (ldata[i] != b0) {
continue;
}
int len = iplen;
int doffset = i;
int poffset = 0;
while (len-- > 0) {
if (ldata[doffset++] != bap[poffset++]) {
continue outer_loop;
}
}
return i + 1;
}
return -1;
}
/**
* Always throws an exception.
*/
public int setBytes(long pos, byte[] bytes) throws SQLException {
throw new SQLException("Not supported");
}
/**
* Always throws an exception.
*/
public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException {
throw new SQLException("Not supported");
}
/**
* Always throws an exception.
*/
public OutputStream setBinaryStream(long pos) throws SQLException {
throw new SQLException("Not supported");
}
/**
* Truncates the <code>BLOB</code> value that this <code>Blob</code> object represents
* to be <code>len</code> bytes in length.
*
* @param len the length, in bytes, to which the <code>BLOB</code> value that this
* <code>Blob</code> object represents should be truncated
* @exception SQLException if there is an error accessing the <code>BLOB</code> value
*/
public void truncate(final long len) throws SQLException {
final byte[] ldata = data;
if (len < 0 || len > ldata.length) {
throw new SQLException("Invalid length: " + Long.toString(len));
}
if (len == ldata.length) {
return;
}
byte[] newData = new byte[(int) len];
System.arraycopy(ldata, 0, newData, 0, (int) len);
data = newData;
}
/**
* @since 3.0
*/
// JDBC 4 compatibility under Java 1.5
public void free() throws SQLException {
}
/**
* @since 3.0
*/
// JDBC 4 compatibility under Java 1.5
public InputStream getBinaryStream(long arg0, long arg1) throws SQLException {
return null;
}
}