diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/AbstractBlob.java | 189 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/AbstractClob.java | 162 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/Blob.java | 190 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/BlobClob.java | 111 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/ByteArrayBlob.java | 419 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/CharArrayClob.java | 432 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/Clob.java | 132 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/FileBlob.java | 112 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/Lob.java | 33 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/StringClob.java | 93 | ||||
| -rw-r--r-- | src/main/java/com/amazon/carbonado/lob/package-info.java | 49 | 
11 files changed, 1922 insertions, 0 deletions
| diff --git a/src/main/java/com/amazon/carbonado/lob/AbstractBlob.java b/src/main/java/com/amazon/carbonado/lob/AbstractBlob.java new file mode 100644 index 0000000..3da80e9 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/AbstractBlob.java @@ -0,0 +1,189 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.DataOutputStream;
 +import java.io.InputStreamReader;
 +import java.io.IOException;
 +import java.io.OutputStreamWriter;
 +import java.io.Reader;
 +import java.io.Writer;
 +
 +import java.nio.charset.Charset;
 +import java.nio.charset.CharsetDecoder;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistException;
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.Transaction;
 +
 +/**
 + * AbstractBlob implements a small set of common Blob methods.
 + *
 + * @author Brian S O'Neill
 + */
 +public abstract class AbstractBlob implements Blob {
 +    private static Charset UTF_8 = Charset.forName("UTF-8");
 +
 +    private final Repository mRepo;
 +
 +    protected AbstractBlob() {
 +        mRepo = null;
 +    }
 +
 +    /**
 +     * Use of this constructor indicates that setValue should operate within a
 +     * transaction. A Repository is passed in for entering the transaction.
 +     *
 +     * @param repo optional repository to use for performing string conversion
 +     * within transactions
 +     */
 +    protected AbstractBlob(Repository repo) {
 +        mRepo = repo;
 +    }
 +
 +    public String asString() throws FetchException {
 +        return asString(UTF_8);
 +    }
 +
 +    public String asString(String charsetName) throws FetchException {
 +        return asString(Charset.forName(charsetName));
 +    }
 +
 +    public String asString(Charset charset) throws FetchException {
 +        Transaction txn = mRepo == null ? null : mRepo.enterTransaction();
 +        try {
 +            long length = getLength();
 +
 +            if (length > Integer.MAX_VALUE) {
 +                throw new IllegalArgumentException
 +                    ("Blob is too long to fit in a String: " + length);
 +            }
 +
 +            if (length <= 0) {
 +                return "";
 +            }
 +
 +            CharsetDecoder decoder = charset.newDecoder();
 +
 +            long charLength = (long) (decoder.averageCharsPerByte() * length);
 +
 +            if (charLength > Integer.MAX_VALUE) {
 +                charLength = Integer.MAX_VALUE;
 +            }
 +
 +            char[] buffer = new char[(int) charLength];
 +
 +            Reader r = new InputStreamReader(openInputStream(), decoder);
 +
 +            try {
 +                int offset = 0;
 +                int amt;
 +                while ((amt = r.read(buffer, offset, buffer.length - offset)) >= 0) {
 +                    offset += amt;
 +                    if (amt == 0 && offset >= buffer.length) {
 +                        // Expand capacity.
 +                        charLength *= 2;
 +                        if (charLength >= Integer.MAX_VALUE) {
 +                            charLength = Integer.MAX_VALUE;
 +                        }
 +                        if (charLength <= buffer.length) {
 +                            throw new IllegalArgumentException
 +                                ("Blob is too long to fit in a String");
 +                        }
 +                        char[] newBuffer = new char[(int) charLength];
 +                        System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
 +                        buffer = newBuffer;
 +                    }
 +                }
 +
 +                return new String(buffer, 0, offset);
 +            } catch (IOException e) {
 +                throw AbstractClob.toFetchException(e);
 +            }
 +        } finally {
 +            if (txn != null) {
 +                try {
 +                    txn.exit();
 +                } catch (PersistException e) {
 +                    // Don't care.
 +                }
 +            }
 +        }
 +    }
 +
 +    public void setValue(String value) throws PersistException {
 +        setValue(value, UTF_8);
 +    }
 +
 +    public void setValue(String value, String charsetName) throws PersistException {
 +        setValue(value, Charset.forName(charsetName));
 +    }
 +
 +    public void setValue(String value, Charset charset) throws PersistException {
 +        if (value == null) {
 +            throw new IllegalArgumentException("Blob value cannot be null");
 +        }
 +
 +        if (mRepo == null) {
 +            setLength(0);
 +            try {
 +                Writer w = new OutputStreamWriter(openOutputStream(), charset);
 +                w.write(value);
 +                w.close();
 +            } catch (IOException e) {
 +                throw AbstractClob.toPersistException(e);
 +            }
 +        } else {
 +            Transaction txn = mRepo.enterTransaction();
 +            try {
 +                long originalLength = getLength();
 +
 +                int newLength;
 +                try {
 +                    DataOutputStream out = new DataOutputStream(openOutputStream());
 +                    Writer w = new OutputStreamWriter(out, charset);
 +                    w.write(value);
 +                    w.close();
 +                    newLength = out.size();
 +                } catch (IOException e) {
 +                    throw AbstractClob.toPersistException(e);
 +                }
 +
 +                if (newLength < originalLength) {
 +                    // Truncate.
 +                    setLength(newLength);
 +
 +                    // Note: DataOutputStream counts bytes written as an int
 +                    // instead of a long. If String is composed of high unicode
 +                    // characters and encoding is UTF-8 is used, then the maximum
 +                    // supported String length is 715,827,882 characters. I don't
 +                    // expect this to be a real problem however, since the String
 +                    // will consume over 1.3 gigabytes of memory.
 +                }
 +
 +                txn.commit();
 +            } catch (FetchException e) {
 +                throw e.toPersistException();
 +            } finally {
 +                txn.exit();
 +            }
 +        }
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/AbstractClob.java b/src/main/java/com/amazon/carbonado/lob/AbstractClob.java new file mode 100644 index 0000000..3ccb0f7 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/AbstractClob.java @@ -0,0 +1,162 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.IOException;
 +import java.io.Reader;
 +import java.io.Writer;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistException;
 +import com.amazon.carbonado.Repository;
 +import com.amazon.carbonado.Transaction;
 +
 +/**
 + * AbstractClob implements a small set of common Clob methods.
 + *
 + * @author Brian S O'Neill
 + */
 +public abstract class AbstractClob implements Clob {
 +    static FetchException toFetchException(IOException e) {
 +        Throwable cause = e.getCause();
 +        if (cause instanceof FetchException) {
 +            return (FetchException) cause;
 +        }
 +        if (cause == null) {
 +            cause = e;
 +        }
 +        return new FetchException(cause);
 +    }
 +
 +    static PersistException toPersistException(IOException e) {
 +        Throwable cause = e.getCause();
 +        if (cause instanceof PersistException) {
 +            return (PersistException) cause;
 +        }
 +        if (cause == null) {
 +            cause = e;
 +        }
 +        return new PersistException(cause);
 +    }
 +
 +    private final Repository mRepo;
 +
 +    protected AbstractClob() {
 +        mRepo = null;
 +    }
 +
 +    /**
 +     * Use of this constructor indicates that setValue should operate within a
 +     * transaction. A Repository is passed in for entering the transaction.
 +     *
 +     * @param repo optional repository to use for performing string conversion
 +     * within transactions
 +     */
 +    protected AbstractClob(Repository repo) {
 +        mRepo = repo;
 +    }
 +
 +    public String asString() throws FetchException {
 +        Transaction txn = mRepo == null ? null : mRepo.enterTransaction();
 +        try {
 +            long length = getLength();
 +
 +            if (length > Integer.MAX_VALUE) {
 +                throw new IllegalArgumentException
 +                    ("Clob is too long to fit in a String: " + length);
 +            }
 +
 +            int iLen = (int) length;
 +
 +            if (iLen <= 0) {
 +                return "";
 +            }
 +
 +            try {
 +                Reader r = openReader();
 +
 +                char[] buf = new char[iLen];
 +                int offset = 0;
 +                int amt;
 +                while ((amt = r.read(buf, offset, iLen - offset)) > 0) {
 +                    offset += amt;
 +                }
 +                r.close();
 +
 +                if (offset <= 0) {
 +                    return "";
 +                }
 +
 +                return new String(buf, 0, offset);
 +            } catch (IOException e) {
 +                throw toFetchException(e);
 +            }
 +        } finally {
 +            if (txn != null) {
 +                try {
 +                    txn.exit();
 +                } catch (PersistException e) {
 +                    // Don't care.
 +                }
 +            }
 +        }
 +    }
 +
 +    public void setValue(String value) throws PersistException {
 +        if (value == null) {
 +            throw new IllegalArgumentException("Clob value cannot be null");
 +        }
 +
 +        if (mRepo == null) {
 +            setLength(0);
 +            try {
 +                Writer w = openWriter();
 +                w.write(value);
 +                w.close();
 +            } catch (IOException e) {
 +                throw toPersistException(e);
 +            }
 +        } else {
 +            Transaction txn = mRepo.enterTransaction();
 +
 +            try {
 +                long originalLength = getLength();
 +
 +                try {
 +                    Writer w = openWriter();
 +                    w.write(value);
 +                    w.close();
 +                } catch (IOException e) {
 +                    throw toPersistException(e);
 +                }
 +
 +                if (value.length() < originalLength) {
 +                    // Truncate.
 +                    setLength(value.length());
 +                }
 +
 +                txn.commit();
 +            } catch (FetchException e) {
 +                throw e.toPersistException();
 +            } finally {
 +                txn.exit();
 +            }
 +        }
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/Blob.java b/src/main/java/com/amazon/carbonado/lob/Blob.java new file mode 100644 index 0000000..b4bbf84 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/Blob.java @@ -0,0 +1,190 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +
 +import java.nio.charset.Charset;
 +import java.nio.charset.IllegalCharsetNameException;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistException;
 +
 +/**
 + * Provides access to BLOBs, which are Binary Large OBjects. Consider accessing
 + * Blobs within a {@link com.amazon.carbonado.Transaction transaction} scope,
 + * to prevent unexpected updates.
 + *
 + * @author Brian S O'Neill
 + * @see Clob
 + */
 +public interface Blob extends Lob {
 +    /**
 +     * Returns an InputStream for reading Blob data positioned at the
 +     * start. The Blob implementation selects an appropriate buffer size for
 +     * the stream.
 +     *
 +     * @return InputStream for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    InputStream openInputStream() throws FetchException;
 +
 +    /**
 +     * Returns an InputStream for reading Blob data. The Blob implementation
 +     * selects an appropriate buffer size for the stream.
 +     *
 +     * @param pos desired zero-based position to read from
 +     * @return InputStream for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    InputStream openInputStream(long pos) throws FetchException;
 +
 +    /**
 +     * Returns an InputStream for reading Blob data. A suggested buffer size
 +     * must be provided, but it might be ignored by the Blob implementation.
 +     *
 +     * @param pos desired zero-based position to read from
 +     * @param bufferSize suggest that the input stream buffer be at least this large (in bytes)
 +     * @return InputStream for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    InputStream openInputStream(long pos, int bufferSize) throws FetchException;
 +
 +    /**
 +     * Returns the length of this Blob, in bytes.
 +     */
 +    long getLength() throws FetchException;
 +
 +    /**
 +     * Convenience method to capture all the Blob data as a single String,
 +     * assuming UTF-8 encoding. Call within a transaction scope to ensure the
 +     * data does not change while the String is being built.
 +     *
 +     * @throws IllegalArgumentException if resulting String length would be
 +     * greater than Integer.MAX_VALUE
 +     * @throws OutOfMemoryError if not enough memory to hold Blob as a single String
 +     */
 +    String asString() throws FetchException;
 +
 +    /**
 +     * Convenience method to capture all the Blob data as a single String,
 +     * decoded against the given charset. Call within a transaction scope to
 +     * ensure the data does not change while the String is being built.
 +     *
 +     * @param charsetName name of character set to decode String
 +     * @throws IllegalCharsetNameException if the given charset name is illegal
 +     * @throws IllegalArgumentException if resulting String length would be
 +     * greater than Integer.MAX_VALUE
 +     * @throws OutOfMemoryError if not enough memory to hold Blob as a single String
 +     */
 +    String asString(String charsetName) throws FetchException;
 +
 +    /**
 +     * Convenience method to capture all the Blob data as a single String,
 +     * decoded against the given charset. Call within a transaction scope to
 +     * ensure the data does not change while the String is being built.
 +     *
 +     * @param charset character set to decode String
 +     * @throws IllegalArgumentException if resulting String length would be
 +     * greater than Integer.MAX_VALUE
 +     * @throws OutOfMemoryError if not enough memory to hold Blob as a single String
 +     */
 +    String asString(Charset charset) throws FetchException;
 +
 +    /**
 +     * Returns an OutputStream for writing Blob data, positioned at the
 +     * start. The Blob implementation selects an appropriate buffer size for
 +     * the stream.
 +     *
 +     * @return OutputStream for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    OutputStream openOutputStream() throws PersistException;
 +
 +    /**
 +     * Returns an OutputStream for writing Blob data. The Blob implementation
 +     * selects an appropriate buffer size for the stream.
 +     *
 +     * @param pos desired zero-based position to write to
 +     * @return OutputStream for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    OutputStream openOutputStream(long pos) throws PersistException;
 +
 +    /**
 +     * Returns an OutputStream for writing Blob data. A suggested buffer size
 +     * must be provided, but it might be ignored by the Blob implementation.
 +     *
 +     * @param pos desired zero-based position to write to
 +     * @param bufferSize suggest that the output stream buffer be at least this large (in bytes)
 +     * @return OutputStream for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    OutputStream openOutputStream(long pos, int bufferSize) throws PersistException;
 +
 +    /**
 +     * Set the length of this Blob, in bytes. If the new length is shorter, the
 +     * Blob is truncated. If the new length is longer, the Blob is padded with
 +     * zeros.
 +     *
 +     * @param length new length to set to
 +     * @throws IllegalArgumentException if length is negative
 +     * @throws com.amazon.carbonado.PersistDeniedException if Blob is read-only
 +     */
 +    void setLength(long length) throws PersistException;
 +
 +    /**
 +     * Convenience method to overwrite all Blob data with the value of a single
 +     * String, applying UTF-8 encoding. The Blob length may grow or shrink, to
 +     * match the encoded String value. Call within a transaction scope to
 +     * ensure the data and length does not change while the value is set.
 +     *
 +     * @param value Blob is overwritten with this value
 +     * @throws IllegalArgumentException if value is null
 +     */
 +    void setValue(String value) throws PersistException;
 +
 +    /**
 +     * Convenience method to overwrite all Blob data with the value of a single
 +     * String, applying the given charset encoding. The Blob length may grow or
 +     * shrink, to match the encoded String value. Call within a transaction
 +     * scope to ensure the data and length does not change while the value is
 +     * set.
 +     *
 +     * @param value Blob is overwritten with this value
 +     * @param charsetName name of character set to encode String
 +     * @throws IllegalCharsetNameException if the given charset name is illegal
 +     * @throws IllegalArgumentException if value is null
 +     */
 +    void setValue(String value, String charsetName) throws PersistException;
 +
 +    /**
 +     * Convenience method to overwrite all Blob data with the value of a single
 +     * String, applying the given charset encoding. The Blob length may grow or
 +     * shrink, to match the encoded String value. Call within a transaction
 +     * scope to ensure the data and length does not change while the value is
 +     * set.
 +     *
 +     * @param value Blob is overwritten with this value
 +     * @param charset character set to encode String
 +     * @throws IllegalArgumentException if value is null
 +     */
 +    void setValue(String value, Charset charset) throws PersistException;
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/BlobClob.java b/src/main/java/com/amazon/carbonado/lob/BlobClob.java new file mode 100644 index 0000000..9822171 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/BlobClob.java @@ -0,0 +1,111 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.BufferedReader;
 +import java.io.BufferedWriter;
 +import java.io.InputStreamReader;
 +import java.io.OutputStreamWriter;
 +import java.io.Reader;
 +import java.io.Writer;
 +
 +import java.nio.charset.Charset;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistException;
 +
 +/**
 + * A Clob implementation which is backed by a Blob. Data is stored in the Blob
 + * using UTF-16BE encoding.
 + *
 + * @author Brian S O'Neill
 + * @author Bob Loblaw
 + */
 +public class BlobClob extends AbstractClob {
 +    private static final Charset UTF_16BE = Charset.forName("UTF-16BE");
 +
 +    private final Blob mBlob;
 +
 +    /**
 +     * @param blob blob to wrap
 +     */
 +    public BlobClob(Blob blob) {
 +        mBlob = blob;
 +    }
 +
 +    public Reader openReader() throws FetchException {
 +        return new BufferedReader(new InputStreamReader(mBlob.openInputStream(), UTF_16BE));
 +    }
 +
 +    public Reader openReader(long pos) throws FetchException {
 +        return openReader(pos, -1);
 +    }
 +
 +    public Reader openReader(long pos, int bufferSize) throws FetchException {
 +        rangeCheck(pos, "Position");
 +        Reader reader = new InputStreamReader(mBlob.openInputStream(pos << 1, 0), UTF_16BE);
 +        if (bufferSize < 0) {
 +            reader = new BufferedReader(reader);
 +        } else if (bufferSize > 0) {
 +            reader = new BufferedReader(reader, bufferSize);
 +        }
 +        return reader;
 +    }
 +
 +    public long getLength() throws FetchException {
 +        return mBlob.getLength() >> 1;
 +    }
 +
 +    public Writer openWriter() throws PersistException {
 +        return new BufferedWriter(new OutputStreamWriter(mBlob.openOutputStream(), UTF_16BE));
 +    }
 +
 +    public Writer openWriter(long pos) throws PersistException {
 +        return openWriter(pos, -1);
 +    }
 +
 +    public Writer openWriter(long pos, int bufferSize) throws PersistException {
 +        rangeCheck(pos, "Position");
 +        Writer writer = new OutputStreamWriter(mBlob.openOutputStream(pos << 1, 0), UTF_16BE);
 +        if (bufferSize < 0) {
 +            writer = new BufferedWriter(writer);
 +        } else if (bufferSize > 0) {
 +            writer = new BufferedWriter(writer, bufferSize);
 +        }
 +        return writer;
 +    }
 +
 +    public void setLength(long length) throws PersistException {
 +        rangeCheck(length, "Length");
 +        mBlob.setLength(length << 1);
 +    }
 +
 +    protected Blob getWrappedBlob() {
 +        return mBlob;
 +    }
 +
 +    private void rangeCheck(long value, String type) {
 +        if (value < 0) {
 +            throw new IllegalArgumentException(type + " is negative: " + value);
 +        }
 +        if (value > (Long.MAX_VALUE >> 1)) {
 +            throw new IllegalArgumentException(type + " too large: " + value);
 +        }
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/ByteArrayBlob.java b/src/main/java/com/amazon/carbonado/lob/ByteArrayBlob.java new file mode 100644 index 0000000..eadd8e1 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/ByteArrayBlob.java @@ -0,0 +1,419 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.InputStream;
 +import java.io.IOException;
 +import java.io.OutputStream;
 +
 +import java.util.Arrays;
 +
 +import com.amazon.carbonado.PersistException;
 +
 +/**
 + * Implementation of a Blob which is backed by a growable in-memory byte array.
 + *
 + * @author Brian S O'Neill
 + */
 +public class ByteArrayBlob extends AbstractBlob {
 +
 +    private final int mInitialCapacity;
 +    private byte[] mData;
 +    private int mLength;
 +
 +    /**
 +     * Construct a ByteArrayBlob with the given initial capacity.
 +     *
 +     * @param capacity initial capacity of internal byte array
 +     */
 +    public ByteArrayBlob(int capacity) {
 +        if (capacity == 0) {
 +            throw new IllegalArgumentException();
 +        }
 +        mInitialCapacity = capacity;
 +        mData = new byte[capacity];
 +    }
 +
 +    /**
 +     * Construct a ByteArrayBlob initially backed by the given byte array. The
 +     * byte array is not cloned until this ByteArrayBlob grows or shrinks.
 +     *
 +     * @param data initial data backing the Blob
 +     */
 +    public ByteArrayBlob(byte[] data) {
 +        if (data.length == 0) {
 +            throw new IllegalArgumentException();
 +        }
 +        mLength = mInitialCapacity = data.length;
 +        mData = data;
 +    }
 +
 +    /**
 +     * Construct a ByteArrayBlob initially backed by the given byte array. The
 +     * byte array is not cloned until this ByteArrayBlob grows or shrinks.
 +     *
 +     * @param data initial data backing the Blob
 +     * @param length initial length of data
 +     */
 +    public ByteArrayBlob(byte[] data, int length) {
 +        if (data.length < length) {
 +            throw new IllegalArgumentException();
 +        }
 +        mInitialCapacity = data.length;
 +        mData = data;
 +        mLength = length;
 +    }
 +
 +    public InputStream openInputStream() {
 +        return new Input(this, 0);
 +    }
 +
 +    public InputStream openInputStream(long pos) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        return new Input(this, pos);
 +    }
 +
 +    public InputStream openInputStream(long pos, int bufferSize) {
 +        return openInputStream(pos);
 +    }
 +
 +    public synchronized long getLength() {
 +        return mLength;
 +    }
 +
 +    synchronized int read(long pos) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            return -1;
 +        }
 +
 +        int ipos = (int) pos;
 +        if (ipos >= mLength) {
 +            return -1;
 +        }
 +
 +        return mData[ipos];
 +    }
 +
 +    synchronized int read(long pos, byte[] bytes) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (bytes == null) {
 +            throw new IllegalArgumentException("Byte array is null");
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            return -1;
 +        }
 +
 +        int ipos = (int) pos;
 +        if (ipos > mLength) { // the use of '>' instead of '>=' is intentional
 +            return -1;
 +        }
 +
 +        int length = bytes.length;
 +        if (ipos + length > mLength) {
 +            length = mLength - ipos;
 +        }
 +
 +        System.arraycopy(mData, ipos, bytes, 0, length);
 +
 +        return length;
 +    }
 +
 +    synchronized int read(long pos, byte[] bytes, int offset, int length) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (bytes == null) {
 +            throw new IllegalArgumentException("Byte array is null");
 +        }
 +        if (offset < 0) {
 +            throw new IllegalArgumentException("Offset is negative: " + offset);
 +        }
 +        if (length < 0) {
 +            throw new IllegalArgumentException("Length is negative: " + length);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            return -1;
 +        }
 +
 +        int ipos = (int) pos;
 +        if (ipos > mLength) { // the use of '>' instead of '>=' is intentional
 +            return -1;
 +        }
 +
 +        if (ipos + length > mLength) {
 +            length = mLength - ipos;
 +        }
 +
 +        try {
 +            System.arraycopy(mData, ipos, bytes, offset, length);
 +        } catch (IndexOutOfBoundsException e) {
 +            if (offset >= bytes.length && length > 0) {
 +                throw new IllegalArgumentException("Offset is too large: " + offset);
 +            }
 +            throw e;
 +        }
 +
 +        return length;
 +    }
 +
 +    public OutputStream openOutputStream() {
 +        return new Output(this, 0);
 +    }
 +
 +    public OutputStream openOutputStream(long pos) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        return new Output(this, pos);
 +    }
 +
 +    public OutputStream openOutputStream(long pos, int bufferSize) {
 +        return openOutputStream(pos);
 +    }
 +
 +    public synchronized void setLength(long length) throws PersistException {
 +        if (length < 0) {
 +            throw new IllegalArgumentException("Length is negative: " + length);
 +        }
 +        if (length > Integer.MAX_VALUE) {
 +            throw new PersistException("Length too long: " + length);
 +        }
 +        int ilength = (int) length;
 +        if (ilength < mLength) {
 +            mLength = ilength;
 +            if (mData.length > mInitialCapacity) {
 +                // Free up some space.
 +                mData = new byte[mInitialCapacity];
 +            }
 +        } else if (ilength > mLength) {
 +            if (ilength <= mData.length) {
 +                Arrays.fill(mData, mLength, ilength, (byte) 0);
 +                mLength = ilength;
 +            } else {
 +                int newLength = mData.length * 2;
 +                if (newLength < ilength) {
 +                    newLength = ilength;
 +                }
 +                byte[] newData = new byte[newLength];
 +                System.arraycopy(mData, 0, newData, 0, mLength);
 +            }
 +            mLength = ilength;
 +        }
 +    }
 +
 +    synchronized void write(long pos, int b) throws IOException {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            throw new IOException("Position too high: " + pos);
 +        }
 +
 +        int ipos = (int) pos;
 +        ensureLengthForWrite(ipos + 1);
 +        mData[ipos] = (byte) b;
 +    }
 +
 +    synchronized void write(long pos, byte[] bytes) throws IOException {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (bytes == null) {
 +            throw new IllegalArgumentException("Byte array is null");
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            throw new IOException("Position too high: " + pos);
 +        }
 +        if (pos + bytes.length > Integer.MAX_VALUE) {
 +            throw new IOException("Position plus length too high: " + (pos + bytes.length));
 +        }
 +
 +        int ipos = (int) pos;
 +        ensureLengthForWrite(ipos + bytes.length);
 +        System.arraycopy(bytes, 0, mData, ipos, bytes.length);
 +    }
 +
 +    synchronized void write(long pos, byte[] bytes, int offset, int length) throws IOException {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (bytes == null) {
 +            throw new IllegalArgumentException("Byte array is null");
 +        }
 +        if (offset < 0) {
 +            throw new IllegalArgumentException("Offset is negative: " + offset);
 +        }
 +        if (length < 0) {
 +            throw new IllegalArgumentException("Length is negative: " + length);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            throw new IOException("Position too high: " + pos);
 +        }
 +        if (pos + length > Integer.MAX_VALUE) {
 +            throw new IOException("Position plus length too high: " + (pos + length));
 +        }
 +
 +        int ipos = (int) pos;
 +        ensureLengthForWrite(ipos + length);
 +        System.arraycopy(bytes, offset, mData, ipos, length);
 +    }
 +
 +    // Caller must be synchronized
 +    private void ensureLengthForWrite(int ilength) {
 +        if (ilength > mLength) {
 +            if (ilength <= mData.length) {
 +                mLength = ilength;
 +            } else {
 +                int newLength = mData.length * 2;
 +                if (newLength < ilength) {
 +                    newLength = ilength;
 +                }
 +                byte[] newData = new byte[newLength];
 +                System.arraycopy(mData, 0, newData, 0, mLength);
 +                mData = newData;
 +            }
 +            mLength = ilength;
 +        }
 +    }
 +
 +    private static class Input extends InputStream {
 +        private final ByteArrayBlob mBlob;
 +        private long mPos;
 +        private long mMarkPos;
 +
 +        Input(ByteArrayBlob blob, long pos) {
 +            mBlob = blob;
 +            mPos = pos;
 +            mMarkPos = pos;
 +        }
 +
 +        @Override
 +        public int read() {
 +            int b = mBlob.read(mPos);
 +            if (b >= 0) {
 +                mPos++;
 +            }
 +            return b;
 +        }
 +
 +        @Override
 +        public int read(byte[] bytes) {
 +            int length = mBlob.read(mPos, bytes);
 +            if (length > 0) {
 +                mPos += length;
 +            }
 +            return length;
 +        }
 +
 +        @Override
 +        public int read(byte[] bytes, int offset, int length) {
 +            length = mBlob.read(mPos, bytes, offset, length);
 +            if (length > 0) {
 +                mPos += length;
 +            }
 +            return length;
 +        }
 +
 +        @Override
 +        public long skip(long n) {
 +            if (n <= 0) {
 +                return 0;
 +            }
 +
 +            long newPos = mPos + n;
 +            if (newPos < 0) {
 +                newPos = Long.MAX_VALUE;
 +            }
 +
 +            long length = mBlob.getLength();
 +            if (newPos > length) {
 +                newPos = length;
 +            }
 +
 +            n = newPos - mPos;
 +            mPos = newPos;
 +            return n;
 +        }
 +
 +        @Override
 +        public int available() {
 +            long avail = mBlob.getLength() - mPos;
 +            if (avail > Integer.MAX_VALUE) {
 +                return Integer.MAX_VALUE;
 +            }
 +            return (int) avail;
 +        }
 +
 +        @Override
 +        public void mark(int readlimit) {
 +            mMarkPos = mPos;
 +        }
 +
 +        @Override
 +        public void reset() {
 +            mPos = mMarkPos;
 +        }
 +
 +        @Override
 +        public boolean markSupported() {
 +            return true;
 +        }
 +    }
 +
 +    private static class Output extends OutputStream {
 +        private final ByteArrayBlob mBlob;
 +        private long mPos;
 +
 +        Output(ByteArrayBlob blob, long pos) {
 +            mBlob = blob;
 +            mPos = pos;
 +        }
 +
 +        @Override
 +        public void write(int b) throws IOException {
 +            mBlob.write(mPos, b);
 +            mPos++;
 +        }
 +
 +        @Override
 +        public void write(byte[] b) throws IOException {
 +            mBlob.write(mPos, b);
 +            mPos += b.length;
 +        }
 +
 +        @Override
 +        public void write(byte[] b, int offset, int length) throws IOException {
 +            mBlob.write(mPos, b, offset, length);
 +            mPos += length;
 +        }
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/CharArrayClob.java b/src/main/java/com/amazon/carbonado/lob/CharArrayClob.java new file mode 100644 index 0000000..ed6b5e9 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/CharArrayClob.java @@ -0,0 +1,432 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.IOException;
 +import java.io.Reader;
 +import java.io.Writer;
 +
 +import java.util.Arrays;
 +
 +import com.amazon.carbonado.PersistException;
 +
 +/**
 + * Implementation of a Clob which is backed by a growable in-memory character
 + * array.
 + *
 + * @author Brian S O'Neill
 + */
 +public class CharArrayClob extends AbstractClob {
 +
 +    private final int mInitialCapacity;
 +    private char[] mData;
 +    private int mLength;
 +
 +    /**
 +     * Construct a CharArrayClob with the given initial capacity.
 +     *
 +     * @param capacity initial capacity of internal character array
 +     */
 +    public CharArrayClob(int capacity) {
 +        if (capacity == 0) {
 +            throw new IllegalArgumentException();
 +        }
 +        mInitialCapacity = capacity;
 +        mData = new char[capacity];
 +    }
 +
 +    /**
 +     * Construct a CharArrayClob initially backed by the given character array. The
 +     * character array is not cloned until this CharArrayClob grows or shrinks.
 +     *
 +     * @param data initial data backing the Clob
 +     */
 +    public CharArrayClob(char[] data) {
 +        if (data.length == 0) {
 +            throw new IllegalArgumentException();
 +        }
 +        mLength = mInitialCapacity = data.length;
 +        mData = data;
 +    }
 +
 +    /**
 +     * Construct a CharArrayClob initially backed by the given character array. The
 +     * character array is not cloned until this CharArrayClob grows or shrinks.
 +     *
 +     * @param data initial data backing the Clob
 +     * @param length initial length of data
 +     */
 +    public CharArrayClob(char[] data, int length) {
 +        if (data.length < length) {
 +            throw new IllegalArgumentException();
 +        }
 +        mInitialCapacity = data.length;
 +        mData = data;
 +        mLength = length;
 +    }
 +
 +    public Reader openReader() {
 +        return new Input(this, 0);
 +    }
 +
 +    public Reader openReader(long pos) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        return new Input(this, pos);
 +    }
 +
 +    public Reader openReader(long pos, int bufferSize) {
 +        return openReader(pos);
 +    }
 +
 +    public synchronized long getLength() {
 +        return mLength;
 +    }
 +
 +    public synchronized String asString() {
 +        return new String(mData, 0, mLength);
 +    }
 +
 +    synchronized int read(long pos) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            return -1;
 +        }
 +
 +        int ipos = (int) pos;
 +        if (ipos >= mLength) {
 +            return -1;
 +        }
 +
 +        return mData[ipos];
 +    }
 +
 +    synchronized int read(long pos, char[] chars) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (chars == null) {
 +            throw new IllegalArgumentException("Character array is null");
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            return -1;
 +        }
 +
 +        int ipos = (int) pos;
 +        if (ipos > mLength) { // the use of '>' instead of '>=' is intentional
 +            return -1;
 +        }
 +
 +        int length = chars.length;
 +        if (ipos + length > mLength) {
 +            length = mLength - ipos;
 +        }
 +
 +        System.arraycopy(mData, ipos, chars, 0, length);
 +
 +        return length;
 +    }
 +
 +    synchronized int read(long pos, char[] chars, int offset, int length) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (chars == null) {
 +            throw new IllegalArgumentException("Character array is null");
 +        }
 +        if (offset < 0) {
 +            throw new IllegalArgumentException("Offset is negative: " + offset);
 +        }
 +        if (length < 0) {
 +            throw new IllegalArgumentException("Length is negative: " + length);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            return -1;
 +        }
 +
 +        int ipos = (int) pos;
 +        if (ipos > mLength) { // the use of '>' instead of '>=' is intentional
 +            return -1;
 +        }
 +
 +        if (ipos + length > mLength) {
 +            length = mLength - ipos;
 +        }
 +
 +        try {
 +            System.arraycopy(mData, ipos, chars, offset, length);
 +        } catch (IndexOutOfBoundsException e) {
 +            if (offset >= chars.length && length > 0) {
 +                throw new IllegalArgumentException("Offset is too large: " + offset);
 +            }
 +            throw e;
 +        }
 +
 +        return length;
 +    }
 +
 +    public Writer openWriter() {
 +        return new Output(this, 0);
 +    }
 +
 +    public Writer openWriter(long pos) {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        return new Output(this, pos);
 +    }
 +
 +    public Writer openWriter(long pos, int bufferSize) {
 +        return openWriter(pos);
 +    }
 +
 +    public synchronized void setLength(long length) throws PersistException {
 +        if (length < 0) {
 +            throw new IllegalArgumentException("Length is negative: " + length);
 +        }
 +        if (length > Integer.MAX_VALUE) {
 +            throw new PersistException("Length too long: " + length);
 +        }
 +        int ilength = (int) length;
 +        if (ilength < mLength) {
 +            mLength = ilength;
 +            if (mData.length > mInitialCapacity) {
 +                // Free up some space.
 +                mData = new char[mInitialCapacity];
 +            }
 +        } else if (ilength > mLength) {
 +            if (ilength <= mData.length) {
 +                Arrays.fill(mData, mLength, ilength, (char) 0);
 +                mLength = ilength;
 +            } else {
 +                int newLength = mData.length * 2;
 +                if (newLength < ilength) {
 +                    newLength = ilength;
 +                }
 +                char[] newData = new char[newLength];
 +                System.arraycopy(mData, 0, newData, 0, mLength);
 +            }
 +            mLength = ilength;
 +        }
 +    }
 +
 +    synchronized void write(long pos, int b) throws IOException {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            throw new IOException("Position too high: " + pos);
 +        }
 +
 +        int ipos = (int) pos;
 +        ensureLengthForWrite(ipos + 1);
 +        mData[ipos] = (char) b;
 +    }
 +
 +    synchronized void write(long pos, char[] chars) throws IOException {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (chars == null) {
 +            throw new IllegalArgumentException("Character array is null");
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            throw new IOException("Position too high: " + pos);
 +        }
 +        if (pos + chars.length > Integer.MAX_VALUE) {
 +            throw new IOException("Position plus length too high: " + (pos + chars.length));
 +        }
 +
 +        int ipos = (int) pos;
 +        ensureLengthForWrite(ipos + chars.length);
 +        System.arraycopy(chars, 0, mData, ipos, chars.length);
 +    }
 +
 +    synchronized void write(long pos, char[] chars, int offset, int length) throws IOException {
 +        if (pos < 0) {
 +            throw new IllegalArgumentException("Position is negative: " + pos);
 +        }
 +        if (chars == null) {
 +            throw new IllegalArgumentException("Character array is null");
 +        }
 +        if (offset < 0) {
 +            throw new IllegalArgumentException("Offset is negative: " + offset);
 +        }
 +        if (length < 0) {
 +            throw new IllegalArgumentException("Length is negative: " + length);
 +        }
 +
 +        if (pos > Integer.MAX_VALUE) {
 +            throw new IOException("Position too high: " + pos);
 +        }
 +        if (pos + length > Integer.MAX_VALUE) {
 +            throw new IOException("Position plus length too high: " + (pos + length));
 +        }
 +
 +        int ipos = (int) pos;
 +        ensureLengthForWrite(ipos + length);
 +        System.arraycopy(chars, offset, mData, ipos, length);
 +    }
 +
 +    // Caller must be synchronized
 +    private void ensureLengthForWrite(int ilength) {
 +        if (ilength > mLength) {
 +            if (ilength <= mData.length) {
 +                mLength = ilength;
 +            } else {
 +                int newLength = mData.length * 2;
 +                if (newLength < ilength) {
 +                    newLength = ilength;
 +                }
 +                char[] newData = new char[newLength];
 +                System.arraycopy(mData, 0, newData, 0, mLength);
 +                mData = newData;
 +            }
 +            mLength = ilength;
 +        }
 +    }
 +
 +    private static class Input extends Reader {
 +        private final CharArrayClob mClob;
 +        private long mPos;
 +        private long mMarkPos;
 +
 +        Input(CharArrayClob blob, long pos) {
 +            mClob = blob;
 +            mPos = pos;
 +            mMarkPos = pos;
 +        }
 +
 +        @Override
 +        public int read() {
 +            int b = mClob.read(mPos);
 +            if (b >= 0) {
 +                mPos++;
 +            }
 +            return b;
 +        }
 +
 +        @Override
 +        public int read(char[] chars) {
 +            int length = mClob.read(mPos, chars);
 +            if (length > 0) {
 +                mPos += length;
 +            }
 +            return length;
 +        }
 +
 +        @Override
 +        public int read(char[] chars, int offset, int length) {
 +            length = mClob.read(mPos, chars, offset, length);
 +            if (length > 0) {
 +                mPos += length;
 +            }
 +            return length;
 +        }
 +
 +        @Override
 +        public long skip(long n) {
 +            if (n <= 0) {
 +                return 0;
 +            }
 +
 +            long newPos = mPos + n;
 +            if (newPos < 0) {
 +                newPos = Long.MAX_VALUE;
 +            }
 +
 +            long length = mClob.getLength();
 +            if (newPos > length) {
 +                newPos = length;
 +            }
 +
 +            n = newPos - mPos;
 +            mPos = newPos;
 +            return n;
 +        }
 +
 +        @Override
 +        public boolean ready() {
 +            return (mClob.getLength() - mPos) > 0;
 +        }
 +
 +        @Override
 +        public void mark(int readlimit) {
 +            mMarkPos = mPos;
 +        }
 +
 +        @Override
 +        public void reset() {
 +            mPos = mMarkPos;
 +        }
 +
 +        @Override
 +        public boolean markSupported() {
 +            return true;
 +        }
 +
 +        @Override
 +        public void close() {
 +        }
 +    }
 +
 +    private static class Output extends Writer {
 +        private final CharArrayClob mClob;
 +        private long mPos;
 +
 +        Output(CharArrayClob blob, long pos) {
 +            mClob = blob;
 +            mPos = pos;
 +        }
 +
 +        @Override
 +        public void write(int b) throws IOException {
 +            mClob.write(mPos, b);
 +            mPos++;
 +        }
 +
 +        @Override
 +        public void write(char[] b) throws IOException {
 +            mClob.write(mPos, b);
 +            mPos += b.length;
 +        }
 +
 +        @Override
 +        public void write(char[] b, int offset, int length) throws IOException {
 +            mClob.write(mPos, b, offset, length);
 +            mPos += length;
 +        }
 +
 +        @Override
 +        public void flush() {
 +        }
 +
 +        @Override
 +        public void close() {
 +        }
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/Clob.java b/src/main/java/com/amazon/carbonado/lob/Clob.java new file mode 100644 index 0000000..744fb6d --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/Clob.java @@ -0,0 +1,132 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.Reader;
 +import java.io.Writer;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistException;
 +
 +/**
 + * Provides access to CLOBs, which are Character Large OBjects. Consider
 + * accessing Clobs within a {@link com.amazon.carbonado.Transaction
 + * transaction} scope, to prevent unexpected updates.
 + *
 + * @author Brian S O'Neill
 + * @see Blob
 + */
 +public interface Clob extends Lob {
 +    /**
 +     * Returns a Reader for reading Clob data, positioned at the start. The
 +     * Clob implementation selects an appropriate buffer size for the reader.
 +     *
 +     * @return Reader for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    Reader openReader() throws FetchException;
 +
 +    /**
 +     * Returns a Reader for reading Clob data. The Clob implementation selects
 +     * an appropriate buffer size for the reader.
 +     *
 +     * @param pos desired zero-based position to read from
 +     * @return Reader for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    Reader openReader(long pos) throws FetchException;
 +
 +    /**
 +     * Returns a Reader for reading Clob data. A suggested buffer size must be
 +     * provided, but it might be ignored by the Clob implementation.
 +     *
 +     * @param pos desired zero-based position to read from
 +     * @param bufferSize suggest that the reader buffer be at least this large (in characters)
 +     * @return Reader for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    Reader openReader(long pos, int bufferSize) throws FetchException;
 +
 +    /**
 +     * Returns the length of this Clob, in characters.
 +     */
 +    long getLength() throws FetchException;
 +
 +    /**
 +     * Convenience method to capture all the Clob data as a single String. Call
 +     * within a transaction scope to ensure the data does not change while the
 +     * String is being built.
 +     *
 +     * @throws IllegalArgumentException if Clob length is greater than Integer.MAX_VALUE
 +     * @throws OutOfMemoryError if not enough memory to hold Clob as a single String
 +     */
 +    String asString() throws FetchException;
 +
 +    /**
 +     * Returns a Writer for writing Clob data, positioned at the start. The
 +     * Clob implementation selects an appropriate buffer size for the writer.
 +     *
 +     * @return Writer for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    Writer openWriter() throws PersistException;
 +
 +    /**
 +     * Returns a Writer for writing Clob data. The Clob implementation selects
 +     * an appropriate buffer size for the writer.
 +     *
 +     * @param pos desired zero-based position to write to
 +     * @return Writer for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    Writer openWriter(long pos) throws PersistException;
 +
 +    /**
 +     * Returns a Writer for writing Clob data. A suggested buffer size must be
 +     * provided, but it might be ignored by the Clob implementation.
 +     *
 +     * @param pos desired zero-based position to write to
 +     * @param bufferSize suggest that the writer buffer be at least this large (in characters)
 +     * @return Writer for this Blob, which is not guaranteed to be thread-safe
 +     * @throws IllegalArgumentException if position is negative
 +     */
 +    Writer openWriter(long pos, int bufferSize) throws PersistException;
 +
 +    /**
 +     * Set the length of this Clob, in characters. If the new length is
 +     * shorter, the Clob is truncated. If the new length is longer, the Clob is
 +     * padded with '\0' characters.
 +     *
 +     * @param length new length to set to
 +     * @throws IllegalArgumentException if length is negative
 +     * @throws com.amazon.carbonado.PersistDeniedException if Clob is read-only
 +     */
 +    void setLength(long length) throws PersistException;
 +
 +    /**
 +     * Convenience method to overwrite all Clob data with the value of a single
 +     * String. The Clob length may grow or shrink, to match the String
 +     * value. Call within a transaction scope to ensure the data and length
 +     * does not change while the value is set.
 +     *
 +     * @param value Clob is overwritten with this value
 +     * @throws IllegalArgumentException if value is null
 +     */
 +    void setValue(String value) throws PersistException;
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/FileBlob.java b/src/main/java/com/amazon/carbonado/lob/FileBlob.java new file mode 100644 index 0000000..c351361 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/FileBlob.java @@ -0,0 +1,112 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.BufferedInputStream;
 +import java.io.BufferedOutputStream;
 +import java.io.File;
 +import java.io.InputStream;
 +import java.io.IOException;
 +import java.io.OutputStream;
 +import java.io.RandomAccessFile;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistException;
 +
 +import com.amazon.carbonado.spi.RAFInputStream;
 +import com.amazon.carbonado.spi.RAFOutputStream;
 +
 +/**
 + * Implementation of a Blob which is backed by a File.
 + *
 + * @author Brian S O'Neill
 + */
 +public class FileBlob extends AbstractBlob {
 +    private final File mFile;
 +
 +    public FileBlob(File file) {
 +        mFile = file;
 +    }
 +
 +    public InputStream openInputStream() throws FetchException {
 +        return openInputStream(0, -1);
 +    }
 +
 +    public InputStream openInputStream(long pos) throws FetchException {
 +        return openInputStream(pos, -1);
 +    }
 +
 +    public InputStream openInputStream(long pos, int bufferSize) throws FetchException {
 +        try {
 +            RandomAccessFile raf = new RandomAccessFile(mFile, "r");
 +            if (pos != 0) {
 +                raf.seek(pos);
 +            }
 +            InputStream in = new RAFInputStream(raf);
 +            if (bufferSize < 0) {
 +                in = new BufferedInputStream(in);
 +            } else if (bufferSize > 0) {
 +                in = new BufferedInputStream(in, bufferSize);
 +            }
 +            return in;
 +        } catch (IOException e) {
 +            throw new FetchException(e);
 +        }
 +    }
 +
 +    public long getLength() throws FetchException {
 +        return mFile.length();
 +    }
 +
 +    public OutputStream openOutputStream() throws PersistException {
 +        return openOutputStream(0, -1);
 +    }
 +
 +    public OutputStream openOutputStream(long pos) throws PersistException {
 +        return openOutputStream(pos, -1);
 +    }
 +
 +    public OutputStream openOutputStream(long pos, int bufferSize) throws PersistException {
 +        try {
 +            RandomAccessFile raf = new RandomAccessFile(mFile, "rw");
 +            if (pos != 0) {
 +                raf.seek(pos);
 +            }
 +            OutputStream out = new RAFOutputStream(raf);
 +            if (bufferSize < 0) {
 +                out = new BufferedOutputStream(out);
 +            } else if (bufferSize > 0) {
 +                out = new BufferedOutputStream(out, bufferSize);
 +            }
 +            return out;
 +        } catch (IOException e) {
 +            throw new PersistException(e);
 +        }
 +    }
 +
 +    public void setLength(long length) throws PersistException {
 +        try {
 +            RandomAccessFile raf = new RandomAccessFile(mFile, "rw");
 +            raf.setLength(length);
 +            raf.close();
 +        } catch (IOException e) {
 +            throw new PersistException(e);
 +        }
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/Lob.java b/src/main/java/com/amazon/carbonado/lob/Lob.java new file mode 100644 index 0000000..a09bfe0 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/Lob.java @@ -0,0 +1,33 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +/**
 + * Marker interface for {@link Blob Blobs} and {@link Clob Clobs}.
 + *
 + * @author Brian S O'Neill
 + */
 +public interface Lob {
 +    /**
 +     * Two Lobs are considered equal if the object instances are the same or if
 +     * they point to the same content. Lob data is not compared, as that would
 +     * be expensive or it may result in a fetch exception.
 +     */
 +    boolean equals(Object obj);
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/StringClob.java b/src/main/java/com/amazon/carbonado/lob/StringClob.java new file mode 100644 index 0000000..f19d63d --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/StringClob.java @@ -0,0 +1,93 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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 com.amazon.carbonado.lob;
 +
 +import java.io.IOException;
 +import java.io.Reader;
 +import java.io.StringReader;
 +import java.io.Writer;
 +
 +import com.amazon.carbonado.FetchException;
 +import com.amazon.carbonado.PersistDeniedException;
 +import com.amazon.carbonado.PersistException;
 +
 +/**
 + * Implementation of a Clob which is backed by a read-only String.
 + *
 + * @author Brian S O'Neill
 + */
 +public class StringClob extends AbstractClob {
 +    // TODO: Make this copy-on-write via internal CharArrayClob.
 +
 +    private final String mStr;
 +
 +    public StringClob(String str) {
 +        mStr = str;
 +    }
 +
 +    public Reader openReader() {
 +        return new StringReader(mStr);
 +    }
 +
 +    public Reader openReader(long pos) throws FetchException {
 +        StringReader r = new StringReader(mStr);
 +        try {
 +            r.skip(pos);
 +        } catch (IOException e) {
 +            throw new FetchException(e);
 +        }
 +        return r;
 +    }
 +
 +    public Reader openReader(long pos, int bufferSize) throws FetchException {
 +        return openReader(pos);
 +    }
 +
 +    public long getLength() throws FetchException {
 +        return mStr.length();
 +    }
 +
 +    public String asString() {
 +        return mStr;
 +    }
 +
 +    public Writer openWriter() throws PersistException {
 +        throw denied();
 +    }
 +
 +    public Writer openWriter(long pos) throws PersistException {
 +        throw denied();
 +    }
 +
 +    public Writer openWriter(long pos, int bufferSize) throws PersistException {
 +        throw denied();
 +    }
 +
 +    public void setLength(long length) throws PersistException {
 +        throw denied();
 +    }
 +
 +    public void setValue(String value) throws PersistException {
 +        denied();
 +    }
 +
 +    private PersistException denied() {
 +        return new PersistDeniedException("Read-only");
 +    }
 +}
 diff --git a/src/main/java/com/amazon/carbonado/lob/package-info.java b/src/main/java/com/amazon/carbonado/lob/package-info.java new file mode 100644 index 0000000..da07059 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/lob/package-info.java @@ -0,0 +1,49 @@ +/*
 + * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 + * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 + *
 + * Licensed 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.
 + */
 +
 +/**
 + * Support for LOB property types, which are Large OBjects. Properties declared
 + * as {@link com.amazon.carbonado.lob.Blob Blob} or {@link
 + * com.amazon.carbonado.lob.Clob Clob} are treated differently than regular
 + * properties. In particular:
 + *
 + * <ul>
 + * <li>Repository typically stores LOB data external from enclosing storable
 + * <li>LOBs are accessed in a manner similar to how files are accessed
 + * <li>LOB data is often read/written in chunks, so consider accessing in a transaction scope
 + * <li>LOBs cannot be annotated with {@link com.amazon.carbonado.PrimaryKey
 + * PrimaryKey}, {@link com.amazon.carbonado.Key Key}, {@link
 + * com.amazon.carbonado.Index Index}, {@link com.amazon.carbonado.Join Join},
 + * {@link com.amazon.carbonado.Version Version}, or {@link
 + * com.amazon.carbonado.Sequence Sequence}
 + * <li>LOBs cannot be used in a {@link com.amazon.carbonado.Storage#query(String) query filter}
 + * </ul>
 + *
 + * <p>Also, setting a LOB property does not dirty that property unless the new
 + * LOB is unequal. Updating a LOB property typically involves operating on the
 + * LOB itself. Setting the LOB property again is useful only when completely
 + * replacing the data, which can be a relatively expensive operation.
 + *
 + * <p>Some repositories require that large text data be stored as a LOB. If the
 + * text property is intended to fit entirely in memory, consider defining the
 + * property as a String instead of a LOB. This allows the repository to decide
 + * if it is appropriate to store it as a LOB. If explicit control over charset
 + * encoding is required, add a {@link com.amazon.carbonado.adapter.TextAdapter
 + * TextAdapter} annotation.
 + */
 +package com.amazon.carbonado.lob;
 | 
