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