InMemoryBlobSupport.java
package org.codefilarete.stalactite.sql.statement.binder;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.SQLException;
import org.codefilarete.tool.collection.Arrays;
/**
* Implementation of {@link Blob} based on in-memory storage which is far from optimized. Far from perfect :
* - {@link SQLException} should be thrown when arguments are not into the bounds of internal buffer
* - optimization could be done on {@link #setBinaryStream(long)}.write because returned {@link OutputStream} doesn't override {@link OutputStream#write(byte[], int, int)}
*
* @author Guillaume Mary
*/
public class InMemoryBlobSupport implements Blob {
private byte[] buffer;
public InMemoryBlobSupport(int length) {
this(new byte[length]);
}
public InMemoryBlobSupport(byte[] buffer) {
this.buffer = buffer;
}
public byte[] getBuffer() {
return buffer;
}
@Override
public long length() throws SQLException {
assertNotFreed();
return buffer.length;
}
@Override
public byte[] getBytes(long pos, int length) throws SQLException {
assertNotFreed();
byte[] result = new byte[length];
System.arraycopy(buffer, (int) pos-1, result, 0, length);
return result;
}
@Override
public InputStream getBinaryStream() throws SQLException {
assertNotFreed();
return new ByteArrayInputStream(buffer);
}
@Override
public InputStream getBinaryStream(long pos, long length) throws SQLException {
if (pos < 1 || pos > length() || (pos + length) > length()) {
throw new SQLException("Incompatible position or length with actual byte count : " + length() + " vs " + pos + " + " + length);
}
return new ByteArrayInputStream(getBytes(pos, (int) length));
}
@Override
public long position(byte[] pattern, long start) throws SQLException {
assertNotFreed();
boolean found = true;
int currIdx = (int) start-1; // -1 because our internal array starts at 0 and argument starts at 1
int offset = 0;
while(found && currIdx < this.buffer.length && offset < pattern.length) {
found = this.buffer[currIdx] == pattern[offset];
offset++;
currIdx++;
}
if (offset != pattern.length) {
return position(pattern, start +1);
} else {
return (long) currIdx - offset + 1;
}
}
@Override
public long position(Blob pattern, long start) throws SQLException {
return position(pattern.getBytes(1, (int) pattern.length()), start);
}
@Override
public int setBytes(long pos, byte[] bytes) throws SQLException {
return setBytes(pos, bytes, 0, bytes.length);
}
@Override
public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException {
assertNotFreed();
if (pos + len > buffer.length) {
resizeBuffer((int) (pos - 1 + len));
}
for (int i = 0; i < len; i++) {
this.buffer[(int) (pos-1 + i)] = bytes[i + offset];
}
return len;
}
private void resizeBuffer(int newLength) {
byte[] extendedBuffer = new byte[newLength];
System.arraycopy(this.buffer, 0, extendedBuffer, 0, this.buffer.length);
this.buffer = extendedBuffer;
}
private void extendBuffer(int chunckSize) {
resizeBuffer(this.buffer.length + chunckSize);
}
@Override
public OutputStream setBinaryStream(long pos) throws SQLException {
assertNotFreed();
return new OutputStream() {
private int offset = (int) pos-1;
// should override write(byte[], int, int) for performance optimization
@Override
public void write(int b) throws IOException {
if (offset == buffer.length) {
extendBuffer(1);
}
buffer[offset++] = (byte) b;
}
};
}
@Override
public void truncate(long len) throws SQLException {
assertNotFreed();
if (len < this.buffer.length) {
this.buffer = Arrays.head(this.buffer, (int) len);
}
}
@Override
public void free() throws SQLException {
this.buffer = null;
}
private void assertNotFreed() throws SQLException {
if (this.buffer == null) {
throw new SQLException("Blob data is no more available because it was freed");
}
}
}