001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2005 Emic Networks
004: * Contact: sequoia@continuent.org
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * Initial developer(s): Marc Herbert
019: * Contributor(s): ______________________.
020: */package org.continuent.sequoia.common.protocol;
021:
022: import java.io.ByteArrayInputStream;
023: import java.io.DataInputStream;
024: import java.io.FileInputStream;
025: import java.io.IOException;
026: import java.io.Serializable;
027: import java.sql.SQLException;
028:
029: import org.continuent.sequoia.common.exceptions.NotImplementedException;
030: import org.continuent.sequoia.common.exceptions.driver.DriverSQLException;
031:
032: /**
033: * The representation (mapping) in the Java <sup><small>TM </small> </sup>
034: * programming language of an SQL <code>BLOB</code> value. By default drivers
035: * implement <code>Blob</code> using an SQL <code>locator(BLOB)</code>,
036: * which means that a <code>Blob</code> object contains a logical pointer to
037: * the SQL <code>BLOB</code> data rather than the data itself. But since this
038: * is highly database-specific, we are unable to do that and implement Blobs
039: * using a simple private byte array copy instead. This may consume a lot of
040: * memory but this is both portable across databases and even legal from a JDBC
041: * standard point of view as long as our method
042: * {@link org.continuent.sequoia.driver.DatabaseMetaData#locatorsUpdateCopy()}
043: * returns true.
044: *
045: * @see java.sql.Blob
046: * @author <a href="mailto:Marc.Herbert@emicnetworks.com">Marc Herbert </a>
047: * @since JDK 1.2
048: */
049: public class ByteArrayBlob implements java.sql.Blob, Serializable {
050: private static final long serialVersionUID = -3473780865755702765L;
051:
052: /** The binary data that makes up this <code>BLOB</code>. */
053: byte[] internalArray;
054:
055: // ------------- JDBC 2.1 / JDK 1.2-------------------
056:
057: /**
058: * @see java.sql.Blob#length()
059: */
060: public long length() throws SQLException {
061: checkInitialized();
062: return internalArray.length;
063: }
064:
065: /**
066: * @see java.sql.Blob#getBytes(long, int)
067: */
068: public byte[] getBytes(long sqlPos, int length) throws SQLException {
069: checkInitialized();
070: checkSQLRangeIsSupported(sqlPos, length);
071:
072: int arrayPos = (int) (sqlPos - 1);
073: return resizedByteArray(internalArray, arrayPos, //
074: Math.min(length, // no more than asked for
075: internalArray.length - arrayPos)); // no more than what we have
076: }
077:
078: /**
079: * @see java.sql.Blob#getBinaryStream()
080: */
081: public java.io.InputStream getBinaryStream() throws SQLException {
082: checkInitialized();
083: return new ByteArrayInputStream(internalArray);
084: }
085:
086: /**
087: * @see java.sql.Blob#position(byte[], long)
088: */
089: public long position(byte[] pattern, long sqlStart)
090: throws SQLException {
091: checkInitialized();
092: checkSQLRangeIsSupported(sqlStart, 0);
093:
094: throw new NotImplementedException(
095: "position not yet implemented");
096: }
097:
098: /**
099: * @see java.sql.Blob#position(java.sql.Blob, long)
100: */
101: public long position(java.sql.Blob pattern, long sqlStart)
102: throws SQLException {
103: checkInitialized();
104: checkSQLRangeIsSupported(sqlStart, 0);
105:
106: // FIXME: implement me
107: return position(pattern.getBytes(0, (int) pattern.length()),
108: sqlStart);
109: }
110:
111: // ------------- JDBC 3.0 / JDK 1.4-------------------
112:
113: /**
114: * @see java.sql.Blob#setBytes(long, byte[], int, int)
115: */
116: public int setBytes(long sqlStartPos, byte[] srcArray)
117: throws SQLException {
118: return this .setBytes(sqlStartPos, srcArray, 0, srcArray.length);
119: }
120:
121: /**
122: * @see java.sql.Blob#setBytes(long, byte[], int, int)
123: */
124: public int setBytes(long sqlStartPos, byte[] srcArray,
125: int srcArrayOffset, int copiedLength) throws SQLException {
126: checkInitialized();
127: checkSQLRangeIsSupported(sqlStartPos, copiedLength);
128:
129: int minimumLengthNeeded = (int) (sqlStartPos - 1)
130: + copiedLength;
131:
132: // If we are too small, let's extend ourselves
133: // FIXME: do the specs say we should do this?
134: if (this .length() < minimumLengthNeeded)
135: internalArray = resizedByteArray(internalArray, 0,
136: minimumLengthNeeded);
137:
138: // else FIXME: when we are "longer", should we remove our tail or keep
139: // it? Do the specs say something about this? Let's keep the tail for now.
140:
141: // Finally copy argument to ourselves.
142: // Bytes between binaryData.length and pos-1 will stay to zero....
143: // FIXME: do the specs say something about this?
144: System.arraycopy(srcArray, srcArrayOffset, internalArray,
145: (int) (sqlStartPos - 1), copiedLength);
146:
147: /*
148: * huh, what else ? OK, something else would make sense in case we don't
149: * extend the array.
150: */
151: return copiedLength;
152: }
153:
154: /**
155: * @see java.sql.Blob#setBinaryStream(long)
156: */
157: public java.io.OutputStream setBinaryStream(long sqlStart)
158: throws SQLException {
159: checkInitialized();
160: checkSQLRangeIsSupported(sqlStart, 0);
161:
162: return new ByteArrayBlobOutputStream(this , (int) (sqlStart - 1));
163: }
164:
165: /**
166: * @see java.sql.Blob#truncate(long)
167: */
168: public void truncate(long newLen) throws SQLException {
169: checkInitialized();
170:
171: if (newLen >= this .length())
172: return;
173:
174: internalArray = resizedByteArray(internalArray, 0, (int) newLen);
175: }
176:
177: // ----------- JDBC 4.0 --------------
178: /**
179: * This method frees the Blob object and releases the resources that it holds.
180: */
181: public void free() {
182: internalArray = null;
183: }
184:
185: // -------- non-standard, convenience constructors --------
186:
187: /**
188: * Creates a new <code>Blob</code> object built from a copy of the given
189: * byte array.
190: *
191: * @param src the array to copy
192: */
193: public ByteArrayBlob(byte[] src) {
194: // just clone the array
195: this .internalArray = resizedByteArray(src, 0, src.length);
196: }
197:
198: /**
199: * Creates a Blob from the given File
200: *
201: * @param file file to read from
202: * @exception IOException if read fails
203: * @exception NotImplementedException file too big to fit into a byte array
204: */
205: public ByteArrayBlob(FileInputStream file) throws IOException,
206: NotImplementedException {
207: long len = file.available();
208: if (len > Integer.MAX_VALUE)
209: throw new NotImplementedException("file too big");
210: internalArray = new byte[(int) len];
211: DataInputStream stream = new DataInputStream(file);
212: stream.readFully(internalArray);
213: }
214:
215: // ------------------ BLOB internals ------------
216:
217: /**
218: * BlobOutputStream needs it
219: */
220: byte[] getInternalByteArray() {
221: return internalArray;
222: }
223:
224: private void checkInitialized() throws DriverSQLException {
225: if (null == internalArray)
226: throw new DriverSQLException("Blob has been freed");
227: }
228:
229: /**
230: * Checks that we can handle SQL indexes (sqlStart) and (sqlEnd =
231: * sqlStart+len-1). Valid sqlStart begins at 1 (SQL-style). A reasonable
232: * sqlEnd of Blob is no more than Integer.MAX_VALUE+1 because we implement
233: * using Java arrays. This method is basically a check to use before casting
234: * from long to int.
235: *
236: * @param sqlStart start index
237: * @param len length
238: * @throws SQLException
239: */
240: static void checkSQLRangeIsSupported(long sqlStart, int len)
241: throws SQLException {
242: long arrayStart = sqlStart - 1;
243: if (arrayStart < 0) {
244: throw new DriverSQLException(
245: "Illegal argument: start of Blob/Clob (" + sqlStart
246: + ") cannot be less than 1");
247: }
248: if (arrayStart + len - 1 > Integer.MAX_VALUE) {
249: throw new NotImplementedException("End of Blob/Clob ("
250: + (sqlStart + len - 1)
251: + ") is too large. Blobs larger than "
252: + Integer.MAX_VALUE + " are not supported");
253: }
254: }
255:
256: /**
257: * Returns a copy of the byte array argument starting at srcFrom and extended
258: * or shortened to newLength. srcFrom index starts from zero (regular style).
259: * <p>
260: * This roughly double the memory used... *sigh*
261: * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4655503 is tagged
262: * "fixed" but nothing changed ?!
263: */
264: private byte[] resizedByteArray(byte[] src, int srcStart,
265: int newSize) {
266: byte[] newArray = new byte[newSize];
267: System.arraycopy(src, srcStart, newArray, 0, Math.min(
268: src.length - srcStart, // don't pass the old size
269: newSize)); // don't pass the new size
270: return newArray;
271: }
272:
273: }
|