001: /*
002: Copyright (C) 2002-2007 MySQL AB
003:
004: This program is free software; you can redistribute it and/or modify
005: it under the terms of version 2 of the GNU General Public License as
006: published by the Free Software Foundation.
007:
008: There are special exceptions to the terms and conditions of the GPL
009: as it is applied to this software. View the full text of the
010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
011: software distribution.
012:
013: This program is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with this program; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021:
022:
023:
024: */
025: package com.mysql.jdbc;
026:
027: import java.io.ByteArrayInputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStream;
031:
032: import java.sql.SQLException;
033:
034: /**
035: * The representation (mapping) in the JavaTM programming language of an SQL
036: * BLOB value. An SQL BLOB is a built-in type that stores a Binary Large Object
037: * as a column value in a row of a database table. The driver implements Blob
038: * using an SQL locator(BLOB), which means that a Blob object contains a logical
039: * pointer to the SQL BLOB data rather than the data itself. A Blob object is
040: * valid for the duration of the transaction in which is was created. Methods in
041: * the interfaces ResultSet, CallableStatement, and PreparedStatement, such as
042: * getBlob and setBlob allow a programmer to access an SQL BLOB value. The Blob
043: * interface provides methods for getting the length of an SQL BLOB (Binary
044: * Large Object) value, for materializing a BLOB value on the client, and for
045: * determining the position of a pattern of bytes within a BLOB value. This
046: * class is new in the JDBC 2.0 API.
047: *
048: * @author Mark Matthews
049: * @version $Id: Blob.java 6437 2007-05-24 20:17:01Z mmatthews $
050: */
051: public class Blob implements java.sql.Blob, OutputStreamWatcher {
052:
053: //
054: // This is a real brain-dead implementation of BLOB. Once I add
055: // streamability to the I/O for MySQL this will be more efficiently
056: // implemented (except for the position() method, ugh).
057: //
058:
059: /** The binary data that makes up this BLOB */
060: private byte[] binaryData = null;
061: private boolean isClosed = false;
062:
063: /**
064: * Creates a Blob without data
065: */
066: Blob() {
067: setBinaryData(Constants.EMPTY_BYTE_ARRAY);
068: }
069:
070: /**
071: * Creates a BLOB encapsulating the given binary data
072: *
073: * @param data
074: * DOCUMENT ME!
075: */
076: Blob(byte[] data) {
077: setBinaryData(data);
078: }
079:
080: /**
081: * Creates an updatable BLOB that can update in-place (not implemented yet).
082: *
083: * @param data
084: * DOCUMENT ME!
085: * @param creatorResultSetToSet
086: * DOCUMENT ME!
087: * @param columnIndexToSet
088: * DOCUMENT ME!
089: */
090: Blob(byte[] data, ResultSetInternalMethods creatorResultSetToSet,
091: int columnIndexToSet) {
092: setBinaryData(data);
093: }
094:
095: private synchronized byte[] getBinaryData() {
096: return this .binaryData;
097: }
098:
099: /**
100: * Retrieves the BLOB designated by this Blob instance as a stream.
101: *
102: * @return this BLOB represented as a binary stream of bytes.
103: *
104: * @throws SQLException
105: * if a database error occurs
106: */
107: public synchronized java.io.InputStream getBinaryStream()
108: throws SQLException {
109: checkClosed();
110:
111: return new ByteArrayInputStream(getBinaryData());
112: }
113:
114: /**
115: * Returns as an array of bytes, part or all of the BLOB value that this
116: * Blob object designates.
117: *
118: * @param pos
119: * where to start the part of the BLOB
120: * @param length
121: * the length of the part of the BLOB you want returned.
122: *
123: * @return the bytes stored in the blob starting at position
124: * <code>pos</code> and having a length of <code>length</code>.
125: *
126: * @throws SQLException
127: * if a database error occurs
128: */
129: public synchronized byte[] getBytes(long pos, int length)
130: throws SQLException {
131: checkClosed();
132:
133: if (pos < 1) {
134: throw SQLError.createSQLException(Messages
135: .getString("Blob.2"), //$NON-NLS-1$
136: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
137: }
138:
139: pos--;
140:
141: if (pos > this .binaryData.length) {
142: throw SQLError
143: .createSQLException(
144: "\"pos\" argument can not be larger than the BLOB's length.",
145: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
146: }
147:
148: if (pos + length > this .binaryData.length) {
149: throw SQLError
150: .createSQLException(
151: "\"pos\" + \"length\" arguments can not be larger than the BLOB's length.",
152: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
153: }
154:
155: byte[] newData = new byte[length];
156: System.arraycopy(getBinaryData(), (int) (pos), newData, 0,
157: length);
158:
159: return newData;
160: }
161:
162: /**
163: * Returns the number of bytes in the BLOB value designated by this Blob
164: * object.
165: *
166: * @return the length of this blob
167: *
168: * @throws SQLException
169: * if a database error occurs
170: */
171: public synchronized long length() throws SQLException {
172: checkClosed();
173:
174: return getBinaryData().length;
175: }
176:
177: /**
178: * @see java.sql.Blob#position(byte[], long)
179: */
180: public synchronized long position(byte[] pattern, long start)
181: throws SQLException {
182: throw SQLError.createSQLException("Not implemented"); //$NON-NLS-1$
183: }
184:
185: /**
186: * Finds the position of the given pattern in this BLOB.
187: *
188: * @param pattern
189: * the pattern to find
190: * @param start
191: * where to start finding the pattern
192: *
193: * @return the position where the pattern is found in the BLOB, -1 if not
194: * found
195: *
196: * @throws SQLException
197: * if a database error occurs
198: */
199: public synchronized long position(java.sql.Blob pattern, long start)
200: throws SQLException {
201: checkClosed();
202:
203: return position(pattern.getBytes(0, (int) pattern.length()),
204: start);
205: }
206:
207: private synchronized void setBinaryData(byte[] newBinaryData) {
208: this .binaryData = newBinaryData;
209: }
210:
211: /**
212: * @see Blob#setBinaryStream(long)
213: */
214: public synchronized OutputStream setBinaryStream(long indexToWriteAt)
215: throws SQLException {
216: checkClosed();
217:
218: if (indexToWriteAt < 1) {
219: throw SQLError.createSQLException(Messages
220: .getString("Blob.0"), //$NON-NLS-1$
221: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
222: }
223:
224: WatchableOutputStream bytesOut = new WatchableOutputStream();
225: bytesOut.setWatcher(this );
226:
227: if (indexToWriteAt > 0) {
228: bytesOut.write(this .binaryData, 0,
229: (int) (indexToWriteAt - 1));
230: }
231:
232: return bytesOut;
233: }
234:
235: /**
236: * @see Blob#setBytes(long, byte[])
237: */
238: public synchronized int setBytes(long writeAt, byte[] bytes)
239: throws SQLException {
240: checkClosed();
241:
242: return setBytes(writeAt, bytes, 0, bytes.length);
243: }
244:
245: /**
246: * @see Blob#setBytes(long, byte[], int, int)
247: */
248: public synchronized int setBytes(long writeAt, byte[] bytes,
249: int offset, int length) throws SQLException {
250: checkClosed();
251:
252: OutputStream bytesOut = setBinaryStream(writeAt);
253:
254: try {
255: bytesOut.write(bytes, offset, length);
256: } catch (IOException ioEx) {
257: throw SQLError.createSQLException(Messages
258: .getString("Blob.1"), //$NON-NLS-1$
259: SQLError.SQL_STATE_GENERAL_ERROR);
260: } finally {
261: try {
262: bytesOut.close();
263: } catch (IOException doNothing) {
264: ; // do nothing
265: }
266: }
267:
268: return length;
269: }
270:
271: /**
272: * @see com.mysql.jdbc.OutputStreamWatcher#streamClosed(byte[])
273: */
274: public synchronized void streamClosed(byte[] byteData) {
275: this .binaryData = byteData;
276: }
277:
278: /**
279: * @see com.mysql.jdbc.OutputStreamWatcher#streamClosed(byte[])
280: */
281: public synchronized void streamClosed(WatchableOutputStream out) {
282: int streamSize = out.size();
283:
284: if (streamSize < this .binaryData.length) {
285: out.write(this .binaryData, streamSize,
286: this .binaryData.length - streamSize);
287: }
288:
289: this .binaryData = out.toByteArray();
290: }
291:
292: /**
293: * Truncates the <code>BLOB</code> value that this <code>Blob</code>
294: * object represents to be <code>len</code> bytes in length.
295: * <p>
296: * <b>Note:</b> If the value specified for <code>len</code>
297: * is greater then the length+1 of the <code>BLOB</code> value then the
298: * behavior is undefined. Some JDBC drivers may throw a
299: * <code>SQLException</code> while other drivers may support this
300: * operation.
301: *
302: * @param len the length, in bytes, to which the <code>BLOB</code> value
303: * that this <code>Blob</code> object represents should be truncated
304: * @exception SQLException if there is an error accessing the
305: * <code>BLOB</code> value or if len is less than 0
306: * @exception SQLFeatureNotSupportedException if the JDBC driver does not support
307: * this method
308: * @since 1.4
309: */
310: public synchronized void truncate(long len) throws SQLException {
311: checkClosed();
312:
313: if (len < 1) {
314: throw SQLError.createSQLException(
315: "\"len\" argument can not be < 1.",
316: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
317: }
318:
319: if (len > this .binaryData.length) {
320: throw SQLError
321: .createSQLException(
322: "\"len\" argument can not be larger than the BLOB's length.",
323: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
324: }
325:
326: // TODO: Do this without copying byte[]s by maintaining some end pointer
327: // on the original data
328:
329: byte[] newData = new byte[(int) len];
330: System.arraycopy(getBinaryData(), 0, newData, 0, (int) len);
331: this .binaryData = newData;
332: }
333:
334: /**
335: * This method frees the <code>Blob</code> object and releases the resources that
336: * it holds. The object is invalid once the <code>free</code>
337: * method is called.
338: *<p>
339: * After <code>free</code> has been called, any attempt to invoke a
340: * method other than <code>free</code> will result in a <code>SQLException</code>
341: * being thrown. If <code>free</code> is called multiple times, the subsequent
342: * calls to <code>free</code> are treated as a no-op.
343: *<p>
344: *
345: * @throws SQLException if an error occurs releasing
346: * the Blob's resources
347: * @exception SQLFeatureNotSupportedException if the JDBC driver does not support
348: * this method
349: * @since 1.6
350: */
351:
352: public synchronized void free() throws SQLException {
353: this .binaryData = null;
354: this .isClosed = true;
355: }
356:
357: /**
358: * Returns an <code>InputStream</code> object that contains a partial <code>Blob</code> value,
359: * starting with the byte specified by pos, which is length bytes in length.
360: *
361: * @param pos the offset to the first byte of the partial value to be retrieved.
362: * The first byte in the <code>Blob</code> is at position 1
363: * @param length the length in bytes of the partial value to be retrieved
364: * @return <code>InputStream</code> through which the partial <code>Blob</code> value can be read.
365: * @throws SQLException if pos is less than 1 or if pos is greater than the number of bytes
366: * in the <code>Blob</code> or if pos + length is greater than the number of bytes
367: * in the <code>Blob</code>
368: *
369: * @exception SQLFeatureNotSupportedException if the JDBC driver does not support
370: * this method
371: * @since 1.6
372: */
373: public synchronized InputStream getBinaryStream(long pos,
374: long length) throws SQLException {
375: checkClosed();
376:
377: if (pos < 1) {
378: throw SQLError.createSQLException(
379: "\"pos\" argument can not be < 1.",
380: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
381: }
382:
383: pos--;
384:
385: if (pos > this .binaryData.length) {
386: throw SQLError
387: .createSQLException(
388: "\"pos\" argument can not be larger than the BLOB's length.",
389: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
390: }
391:
392: if (pos + length > this .binaryData.length) {
393: throw SQLError
394: .createSQLException(
395: "\"pos\" + \"length\" arguments can not be larger than the BLOB's length.",
396: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
397: }
398:
399: return new ByteArrayInputStream(getBinaryData(), (int) pos,
400: (int) length);
401: }
402:
403: private synchronized void checkClosed() throws SQLException {
404: if (this .isClosed) {
405: throw SQLError.createSQLException(
406: "Invalid operation on closed BLOB",
407: SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
408: }
409: }
410: }
|