From 21ffac73f08766ea06a289028f7479d04cb0ccff Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 30 Aug 2006 02:06:57 +0000 Subject: Add LOB support --- .../com/amazon/carbonado/lob/AbstractBlob.java | 189 +++++++++ .../com/amazon/carbonado/lob/AbstractClob.java | 162 ++++++++ src/main/java/com/amazon/carbonado/lob/Blob.java | 190 +++++++++ .../java/com/amazon/carbonado/lob/BlobClob.java | 111 ++++++ .../com/amazon/carbonado/lob/ByteArrayBlob.java | 419 ++++++++++++++++++++ .../com/amazon/carbonado/lob/CharArrayClob.java | 432 +++++++++++++++++++++ src/main/java/com/amazon/carbonado/lob/Clob.java | 132 +++++++ .../java/com/amazon/carbonado/lob/FileBlob.java | 112 ++++++ src/main/java/com/amazon/carbonado/lob/Lob.java | 33 ++ .../java/com/amazon/carbonado/lob/StringClob.java | 93 +++++ .../com/amazon/carbonado/lob/package-info.java | 49 +++ 11 files changed, 1922 insertions(+) create mode 100644 src/main/java/com/amazon/carbonado/lob/AbstractBlob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/AbstractClob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/Blob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/BlobClob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/ByteArrayBlob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/CharArrayClob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/Clob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/FileBlob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/Lob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/StringClob.java create mode 100644 src/main/java/com/amazon/carbonado/lob/package-info.java (limited to 'src/main') 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: + * + * + * + *

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. + * + *

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; -- cgit v1.2.3