# 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");
    		}
    	}
    }