/***************************************************************** * 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; } }