class LobLocalStorage implements LobStorage {

    private final DataHandler handler;
    private final LobReader lobReader;

    public LobLocalStorage(DataHandler handler, LobReader lobReader) {
        this.handler = handler;
        this.lobReader = lobReader;
    }

    @Override
    public ValueLob createBlob(InputStream in, long maxLength) {<FILL_FUNCTION_BODY>}

    @Override
    public ValueLob createClob(Reader reader, long maxLength) {
        // need to use a temp file, because the input stream could come from
        // the same database, which would create a weird situation (trying
        // to read a block while writing something)
        return ValueLob.createTempClob(reader, maxLength, handler);
    }

    @Override
    public InputStream getInputStream(ValueLob lob, byte[] hmac, long byteCount) throws IOException {
        if (byteCount < 0) {
            byteCount = Long.MAX_VALUE;
        }
        return new BufferedInputStream(new LobInputStream(lobReader, lob, hmac, byteCount));
    }

    @Override
    public void setTable(ValueLob lob, int tableIdSessionVariable) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void removeAllForTable(int tableId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void removeLob(ValueLob lob) {
        // not stored in the database
    }

    /**
     * An input stream that reads from a remote LOB.
     */
    private static class LobInputStream extends InputStream {

        private final LobReader lobReader;
        private final long lobId;
        private final byte[] hmac;
        private long pos;
        /**
         * The remaining bytes in the lob.
         */
        private long remainingBytes;

        LobInputStream(LobReader lobReader, ValueLob lob, byte[] hmac, long byteCount) {
            this.lobReader = lobReader;
            this.lobId = lob.getLobId();
            this.hmac = hmac;
            remainingBytes = byteCount;
        }

        @Override
        public int read() throws IOException {
            byte[] buff = new byte[1];
            int len = read(buff, 0, 1);
            return len < 0 ? len : (buff[0] & 255);
        }

        @Override
        public int read(byte[] buff) throws IOException {
            return read(buff, 0, buff.length);
        }

        @Override
        public int read(byte[] buff, int off, int length) throws IOException {
            if (length == 0) {
                return 0;
            }
            length = (int) Math.min(length, remainingBytes);
            if (length == 0) {
                return -1;
            }
            try {
                length = lobReader.readLob(lobId, hmac, pos, buff, off, length);
            } catch (DbException e) {
                throw DbException.convertToIOException(e);
            }
            remainingBytes -= length;
            if (length == 0) {
                return -1;
            }
            pos += length;
            return length;
        }

        @Override
        public long skip(long n) {
            remainingBytes -= n;
            pos += n;
            return n;
        }
    }

    public static interface LobReader {
        /**
        * Read from a lob.
        *
        * @param lobId the lob
        * @param hmac the message authentication code
        * @param offset the offset within the lob
        * @param buff the target buffer
        * @param off the offset within the target buffer
        * @param length the number of bytes to read
        * @return the number of bytes read
        */
        int readLob(long lobId, byte[] hmac, long offset, byte[] buff, int off, int length);
    }
}

        // need to use a temp file, because the input stream could come from
        // the same database, which would create a weird situation (trying
        // to read a block while writing something)
        return ValueLob.createTempBlob(in, maxLength, handler);
    
<no_super_class>