summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2006-08-30 02:06:57 +0000
committerBrian S. O'Neill <bronee@gmail.com>2006-08-30 02:06:57 +0000
commit21ffac73f08766ea06a289028f7479d04cb0ccff (patch)
tree88e292be0cb4f8813a075f99062baf3a0eaa9763 /src/main/java/com/amazon/carbonado
parent04b06d8fa2ced539126f74f4d8a19c29b62c5730 (diff)
Add LOB support
Diffstat (limited to 'src/main/java/com/amazon/carbonado')
-rw-r--r--src/main/java/com/amazon/carbonado/lob/AbstractBlob.java189
-rw-r--r--src/main/java/com/amazon/carbonado/lob/AbstractClob.java162
-rw-r--r--src/main/java/com/amazon/carbonado/lob/Blob.java190
-rw-r--r--src/main/java/com/amazon/carbonado/lob/BlobClob.java111
-rw-r--r--src/main/java/com/amazon/carbonado/lob/ByteArrayBlob.java419
-rw-r--r--src/main/java/com/amazon/carbonado/lob/CharArrayClob.java432
-rw-r--r--src/main/java/com/amazon/carbonado/lob/Clob.java132
-rw-r--r--src/main/java/com/amazon/carbonado/lob/FileBlob.java112
-rw-r--r--src/main/java/com/amazon/carbonado/lob/Lob.java33
-rw-r--r--src/main/java/com/amazon/carbonado/lob/StringClob.java93
-rw-r--r--src/main/java/com/amazon/carbonado/lob/package-info.java49
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;