0001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
0002: // Copyright (C) 2004 The jTDS Project
0003: //
0004: // This library is free software; you can redistribute it and/or
0005: // modify it under the terms of the GNU Lesser General Public
0006: // License as published by the Free Software Foundation; either
0007: // version 2.1 of the License, or (at your option) any later version.
0008: //
0009: // This library is distributed in the hope that it will be useful,
0010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: // Lesser General Public License for more details.
0013: //
0014: // You should have received a copy of the GNU Lesser General Public
0015: // License along with this library; if not, write to the Free Software
0016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: //
0018: package net.sourceforge.jtds.util;
0019:
0020: import java.io.*;
0021: import java.sql.SQLException;
0022:
0023: import net.sourceforge.jtds.jdbc.Messages;
0024:
0025: /**
0026: * Manages a buffer (backed by optional disk storage) for use as a data store
0027: * by the CLOB and BLOB objects.
0028: * <p/>
0029: * The data can be purely memory based until the size exceeds the value
0030: * dictated by the <code>lobBuffer</code> URL property after which it will be
0031: * written to disk. The disk array is accessed randomly one page (1024 bytes)
0032: * at a time.
0033: * <p/>
0034: * This class is not synchronized and concurrent open input and output
0035: * streams can conflict.
0036: * <p/>
0037: * Tuning hints:
0038: * <ol>
0039: * <li>The <code>PAGE_SIZE</code> governs how much data is buffered when
0040: * reading or writing data a byte at a time. 1024 bytes seems to work well
0041: * but if very large objects are being written a byte at a time 4096 may be
0042: * better. <b>NB.</b> ensure that the <code>PAGE_MASK</code> and
0043: * <code>BYTE_MASK</code> fields are also adjusted to match.
0044: * <li>Reading or writing byte arrays that are greater than or equal to the
0045: * page size will go directly to or from the random access file cutting out
0046: * an ArrayCopy operation.
0047: * <li>If BLOBs are being buffered exclusively in memory you may wish to
0048: * adjust the <code>MAX_BUF_INC</code> value. Every time the buffer is
0049: * expanded the existing contents are copied and this may get expensive
0050: * with very large BLOBs.
0051: * <li>The BLOB file will be kept open for as long as there are open input or
0052: * output streams. Therefore BLOB streams should be explicitly closed as
0053: * soon as they are finished with.
0054: * </ol>
0055: *
0056: * @author Mike Hutchinson
0057: * @version $Id: BlobBuffer.java,v 1.4 2007/07/08 21:38:14 bheineman Exp $
0058: */
0059: public class BlobBuffer {
0060:
0061: /**
0062: * Default zero length buffer.
0063: */
0064: private static final byte[] EMPTY_BUFFER = new byte[0];
0065: /**
0066: * Default page size (must be power of 2).
0067: */
0068: private static final int PAGE_SIZE = 1024;
0069: /**
0070: * Mask for page component of read/write pointer.
0071: */
0072: private static final int PAGE_MASK = 0xFFFFFC00;
0073: /**
0074: * Mask for page offset component of R/W pointer.
0075: */
0076: private static final int BYTE_MASK = 0x000003FF;
0077: /**
0078: * Maximum buffer increment.
0079: */
0080: private static final int MAX_BUF_INC = 16384;
0081: /**
0082: * Invalid page marker.
0083: */
0084: private static final int INVALID_PAGE = -1;
0085:
0086: /**
0087: * The BLOB buffer or the current page buffer.
0088: */
0089: private byte[] buffer;
0090: /**
0091: * The total length of the valid data in buffer.
0092: */
0093: private int length;
0094: /**
0095: * The number of the current page in memory.
0096: */
0097: private int currentPage;
0098: /**
0099: * The name of the temporary BLOB disk file.
0100: */
0101: private File blobFile;
0102: /**
0103: * The RA file object reference or null if closed.
0104: */
0105: private RandomAccessFile raFile;
0106: /**
0107: * Indicates page in memory must be saved.
0108: */
0109: private boolean bufferDirty;
0110: /**
0111: * Count of callers that have opened the BLOB file.
0112: */
0113: private int openCount;
0114: /**
0115: * True if attempts to create a BLOB file have failed.
0116: */
0117: private boolean isMemOnly;
0118: /**
0119: * The directory to buffer data to.
0120: */
0121: private final File bufferDir;
0122: /**
0123: * The maximum size of an in memory buffer.
0124: */
0125: private final int maxMemSize;
0126:
0127: /**
0128: * Creates a blob buffer.
0129: *
0130: * @param bufferDir
0131: * @param maxMemSize the maximum size of the in memory buffer
0132: */
0133: public BlobBuffer(File bufferDir, long maxMemSize) {
0134: this .bufferDir = bufferDir;
0135: this .maxMemSize = (int) maxMemSize;
0136: buffer = EMPTY_BUFFER;
0137: }
0138:
0139: /**
0140: * Finalizes this object by deleting any work files.
0141: */
0142: protected void finalize() throws Throwable {
0143: try {
0144: if (raFile != null) {
0145: raFile.close();
0146: }
0147: } catch (IOException e) {
0148: // Ignore we are going to delete anyway
0149: } finally {
0150: if (blobFile != null) {
0151: blobFile.delete();
0152: }
0153: }
0154: }
0155:
0156: /**
0157: * Creates a random access disk file to use as backing storage for the LOB
0158: * data.
0159: * <p/>
0160: * This method may fail due to security exceptions or local disk problems,
0161: * in which case the blob storage will remain entirely in memory.
0162: */
0163: public void createBlobFile() {
0164: try {
0165: blobFile = File.createTempFile("jtds", ".tmp", bufferDir);
0166: blobFile.deleteOnExit();
0167: raFile = new RandomAccessFile(blobFile, "rw");
0168: if (length > 0) {
0169: raFile.write(buffer, 0, (int) length);
0170: }
0171: buffer = new byte[PAGE_SIZE];
0172: currentPage = INVALID_PAGE;
0173: openCount = 0;
0174: } catch (SecurityException e) {
0175: blobFile = null;
0176: raFile = null;
0177: isMemOnly = true;
0178: Logger.println("SecurityException creating BLOB file:");
0179: Logger.logException(e);
0180: } catch (IOException ioe) {
0181: blobFile = null;
0182: raFile = null;
0183: isMemOnly = true;
0184: Logger.println("IOException creating BLOB file:");
0185: Logger.logException(ioe);
0186: }
0187: }
0188:
0189: /**
0190: * Opens the BLOB disk file.
0191: * <p/>
0192: * A count of open and close requests is kept so that the file may be
0193: * closed when no longer required thus keeping the number of open files to
0194: * a minimum.
0195: *
0196: * @throws IOException if an I/O error occurs
0197: */
0198: public void open() throws IOException {
0199: if (raFile == null && blobFile != null) {
0200: // reopen file
0201: raFile = new RandomAccessFile(blobFile, "rw");
0202: openCount = 1;
0203: currentPage = INVALID_PAGE;
0204: buffer = new byte[PAGE_SIZE];
0205: return;
0206: }
0207: if (raFile != null) {
0208: openCount++;
0209: }
0210: }
0211:
0212: /**
0213: * Reads byte from the BLOB buffer at the specified location.
0214: * <p/>
0215: * The read pointer is partitioned into a page number and an offset within
0216: * the page. This routine will read new pages as required. The page size
0217: * must be a power of 2 and is currently set to 1024 bytes.
0218: *
0219: * @param readPtr the offset in the buffer of the required byte
0220: * @return the byte value as an <code>int</code> or -1 if at EOF
0221: * @throws IOException if an I/O error occurs
0222: */
0223: public int read(int readPtr) throws IOException {
0224: if (readPtr >= length) {
0225: // At end of file.
0226: return -1;
0227: }
0228: if (raFile != null) {
0229: // Paged storage as a file exists
0230: if (currentPage != (readPtr & PAGE_MASK)) {
0231: // Requested page not in memory so read it
0232: readPage(readPtr);
0233: }
0234: // Use the byte offset to return the correct
0235: // byte from the page.
0236: return buffer[readPtr & BYTE_MASK] & 0xFF;
0237: } else {
0238: // In memory buffer just return byte.
0239: return buffer[readPtr] & 0xFF;
0240: }
0241: }
0242:
0243: /**
0244: * Reads bytes from the BLOB buffer at the specified location.
0245: *
0246: * @param readPtr the offset in the buffer of the required byte
0247: * @param bytes the byte array to fill
0248: * @param offset the start position in the byte array
0249: * @param len the number of bytes to read
0250: * @return the number of bytes read or -1 if at end of file
0251: * @throws IOException if an I/O error occurs
0252: */
0253: public int read(int readPtr, byte[] bytes, int offset, int len)
0254: throws IOException {
0255: // Validate parameters
0256: if (bytes == null) {
0257: throw new NullPointerException();
0258: } else if ((offset < 0) || (offset > bytes.length) || (len < 0)
0259: || ((offset + len) > bytes.length)
0260: || ((offset + len) < 0)) {
0261: throw new IndexOutOfBoundsException();
0262: } else if (len == 0) {
0263: return 0;
0264: }
0265: if (readPtr >= length) {
0266: // At end of file
0267: return -1;
0268: }
0269:
0270: if (raFile != null) {
0271: // Need to read from disk file
0272: len = Math.min(length - readPtr, len);
0273: if (len >= PAGE_SIZE) {
0274: // This is a big write so we optimize by reading directly
0275: // from the RA File.
0276: if (bufferDirty) {
0277: writePage(currentPage);
0278: }
0279: currentPage = INVALID_PAGE;
0280: raFile.seek(readPtr);
0281: raFile.readFully(bytes, offset, len);
0282: } else {
0283: //
0284: // Partial read so buffer locally
0285: //
0286: int count = len;
0287: while (count > 0) {
0288: if (currentPage != (readPtr & PAGE_MASK)) {
0289: // Requested page not in memory so read it
0290: readPage(readPtr);
0291: }
0292: int inBuffer = Math.min(PAGE_SIZE
0293: - (readPtr & BYTE_MASK), count);
0294: System.arraycopy(buffer, readPtr & BYTE_MASK,
0295: bytes, offset, inBuffer);
0296: offset += inBuffer;
0297: readPtr += inBuffer;
0298: count -= inBuffer;
0299: }
0300: }
0301: } else {
0302: // In memory buffer
0303: len = Math.min(length - readPtr, len);
0304: System.arraycopy(buffer, readPtr, bytes, offset, len);
0305: }
0306:
0307: return len;
0308: }
0309:
0310: /**
0311: * Inserts a byte into the buffer at the specified location.
0312: * <p/>
0313: * The write pointer is partitioned into a page number and an offset within
0314: * the page. This routine will write new pages as required. The page size
0315: * must be a power of 2 and is currently set to 1024 bytes.
0316: *
0317: * @param writePtr the offset in the buffer of the required byte
0318: * @param b the byte value to write
0319: * @throws IOException if an I/O error occurs
0320: */
0321: public void write(int writePtr, int b) throws IOException {
0322: if (writePtr >= length) {
0323: if (writePtr > length) {
0324: // Probably because the user called truncate at
0325: // the same time as writing to the blob!
0326: throw new IOException("BLOB buffer has been truncated");
0327: }
0328: // We are writing beyond the current length
0329: // of the buffer and need to update the total length.
0330: if (++length < 0) {
0331: // We have wrapped 31 bits!
0332: // This should ensure that the disk file is limited to 2GB.
0333: // If in memory JVM will probably have failed by now anyway.
0334: throw new IOException("BLOB may not exceed 2GB in size");
0335: }
0336: }
0337:
0338: if (raFile != null) {
0339: // OK we have a disk based buffer
0340: if (currentPage != (writePtr & PAGE_MASK)) {
0341: // The page we need is not in memory
0342: readPage(writePtr);
0343: }
0344: buffer[writePtr & BYTE_MASK] = (byte) b;
0345: // Ensure change will saved if buffer is replaced
0346: bufferDirty = true;
0347: } else {
0348: // In memory buffer only (only used here if disk unavailable
0349: if (writePtr >= buffer.length) {
0350: growBuffer(writePtr + 1);
0351: }
0352: buffer[writePtr] = (byte) b;
0353: }
0354: }
0355:
0356: /**
0357: * Inserts bytes into the buffer at the specified location.
0358: *
0359: * @param writePtr the offset in the buffer of the required byte
0360: * @param bytes the byte array value to write
0361: * @param offset the start position in the byte array
0362: * @param len the number of bytes to write
0363: * @throws IOException if an I/O error occurs
0364: */
0365: void write(int writePtr, byte[] bytes, int offset, int len)
0366: throws IOException {
0367: // Validate parameters
0368: if (bytes == null) {
0369: throw new NullPointerException();
0370: } else if ((offset < 0) || (offset > bytes.length) || (len < 0)
0371: || ((offset + len) > bytes.length)
0372: || ((offset + len) < 0)) {
0373: throw new IndexOutOfBoundsException();
0374: } else if (len == 0) {
0375: return;
0376: }
0377: if ((long) writePtr + len > (long) Integer.MAX_VALUE) {
0378: throw new IOException("BLOB may not exceed 2GB in size");
0379: }
0380: if (writePtr > length) {
0381: // Probably because the user called truncate at
0382: // the same time as writing to the blob!
0383: throw new IOException("BLOB buffer has been truncated");
0384: }
0385:
0386: if (raFile != null) {
0387: // dealing with disk storage (normal case)
0388: //
0389: if (len >= PAGE_SIZE) {
0390: // This is a big write so we optimize by writing directly
0391: // to the RA File.
0392: if (bufferDirty) {
0393: writePage(currentPage);
0394: }
0395: currentPage = INVALID_PAGE;
0396: raFile.seek(writePtr);
0397: raFile.write(bytes, offset, len);
0398: writePtr += len;
0399: } else {
0400: // Small writes so use the page buffer for
0401: // effeciency.
0402: int count = len;
0403: while (count > 0) {
0404: // Paged storage as a file exists
0405: if (currentPage != (writePtr & PAGE_MASK)) {
0406: // Requested page not in memory so read it
0407: readPage(writePtr);
0408: }
0409: int inBuffer = Math.min(PAGE_SIZE
0410: - (writePtr & BYTE_MASK), count);
0411: System.arraycopy(bytes, offset, buffer, writePtr
0412: & BYTE_MASK, inBuffer);
0413: bufferDirty = true;
0414: offset += inBuffer;
0415: writePtr += inBuffer;
0416: count -= inBuffer;
0417: }
0418: }
0419: } else {
0420: // In memory (only used here if disk not available)
0421: if (writePtr + len > buffer.length) {
0422: growBuffer(writePtr + len);
0423: }
0424: System.arraycopy(bytes, offset, buffer, writePtr, len);
0425: writePtr += len;
0426: }
0427: if (writePtr > length) {
0428: length = writePtr;
0429: }
0430: }
0431:
0432: /**
0433: * Reads in the specified page from the disk buffer.
0434: * <p/>
0435: * Any existing dirty page is first saved to disk.
0436: *
0437: * @param page the page number
0438: * @throws IOException if an I/O error occurs
0439: */
0440: public void readPage(int page) throws IOException {
0441: page = page & PAGE_MASK;
0442: if (bufferDirty) {
0443: writePage(currentPage);
0444: }
0445: if (page > raFile.length()) {
0446: throw new IOException("readPage: Invalid page number "
0447: + page);
0448: }
0449: currentPage = page;
0450: // Locate and read requested page
0451: // NB. Page may not be completely filled.
0452: raFile.seek(currentPage);
0453: // Repeat reading until buffer is filled or EOF is reached
0454: int count = 0, res;
0455: do {
0456: res = raFile.read(buffer, count, buffer.length - count);
0457: count += (res == -1) ? 0 : res;
0458: } while (count < PAGE_SIZE && res != -1);
0459: }
0460:
0461: /**
0462: * Writes the specified page to the disk buffer.
0463: *
0464: * @param page the page number
0465: * @throws IOException if an I/O error occurs
0466: */
0467: public void writePage(int page) throws IOException {
0468: page = page & PAGE_MASK;
0469: if (page > raFile.length()) {
0470: throw new IOException("writePage: Invalid page number "
0471: + page);
0472: }
0473: if (buffer.length != PAGE_SIZE) {
0474: throw new IllegalStateException(
0475: "writePage: buffer size invalid");
0476: }
0477: raFile.seek(page);
0478: raFile.write(buffer);
0479: bufferDirty = false;
0480: }
0481:
0482: /**
0483: * Logically closes the file or physically close it if the open count is
0484: * now zero.
0485: * <p/>
0486: * Any updated buffer in memory is flushed to disk before the file is
0487: * closed.
0488: *
0489: * @throws IOException if an I/O error occurs
0490: */
0491: public void close() throws IOException {
0492: if (openCount > 0) {
0493: if (--openCount == 0 && raFile != null) {
0494: if (bufferDirty) {
0495: writePage(currentPage);
0496: }
0497: raFile.close();
0498: raFile = null;
0499: // Allow buffer to be garbage collected
0500: buffer = EMPTY_BUFFER;
0501: currentPage = INVALID_PAGE;
0502: }
0503: }
0504: }
0505:
0506: /**
0507: * Increases the size of the in memory buffer for situations where disk
0508: * storage of BLOB is not possible.
0509: *
0510: * @param minSize the minimum size of buffer required
0511: */
0512: public void growBuffer(int minSize) {
0513: if (buffer.length == 0) {
0514: // Assign initial buffer
0515: buffer = new byte[Math.max(PAGE_SIZE, minSize)];
0516: } else {
0517: byte[] tmp;
0518: if (buffer.length * 2 > minSize
0519: && buffer.length <= MAX_BUF_INC) {
0520: tmp = new byte[buffer.length * 2];
0521: } else {
0522: tmp = new byte[minSize + MAX_BUF_INC];
0523: }
0524: // Copy over existing data
0525: System.arraycopy(buffer, 0, tmp, 0, buffer.length);
0526: buffer = tmp; // Assign new buffer.
0527: }
0528: }
0529:
0530: /**
0531: * Sets the initial buffer to an existing byte array.
0532: *
0533: * @param bytes the byte array containing the BLOB data
0534: * @param copy true if a local copy of the data is required
0535: */
0536: public void setBuffer(byte[] bytes, boolean copy) {
0537: if (copy) {
0538: this .buffer = new byte[bytes.length];
0539: System.arraycopy(bytes, 0, this .buffer, 0, buffer.length);
0540: } else {
0541: this .buffer = bytes;
0542: }
0543: this .length = buffer.length;
0544: }
0545:
0546: //
0547: // ---- Inner classes implementing the various input/output stream classes ---
0548: //
0549:
0550: /**
0551: * An <code>InputStream</code> over the BLOB buffer.
0552: */
0553: private class BlobInputStream extends InputStream {
0554: private int readPtr;
0555: private boolean open;
0556:
0557: /**
0558: * Costructs an <code>InputStream</code> object over the BLOB buffer.
0559: *
0560: * @param pos the starting position (from 0)
0561: * @throws IOException if an I/O error occurs
0562: */
0563: public BlobInputStream(long pos) throws IOException {
0564: BlobBuffer.this .open();
0565: open = true;
0566: readPtr = (int) pos;
0567: }
0568:
0569: /**
0570: * Ensures underlying BLOB file can be closed even if user does not
0571: * close this stream.
0572: */
0573: protected void finalize() throws Throwable {
0574: if (open) {
0575: try {
0576: close();
0577: } catch (IOException e) {
0578: // Ignore closing anyway
0579: } finally {
0580: super .finalize();
0581: }
0582: }
0583: }
0584:
0585: /**
0586: * Returns the number of bytes available to read.
0587: *
0588: * @throws IOException if an I/O error occurs
0589: */
0590: public int available() throws IOException {
0591: return (int) BlobBuffer.this .getLength() - readPtr;
0592: }
0593:
0594: /**
0595: * Reads the next byte from the stream.
0596: *
0597: * @return the next byte as an <code>int</code> or -1 if at EOF
0598: * @throws IOException if an I/O error occurs
0599: */
0600: public int read() throws IOException {
0601: int b = BlobBuffer.this .read(readPtr);
0602: if (b >= 0) {
0603: readPtr++;
0604: }
0605: return b;
0606: }
0607:
0608: /**
0609: * Reads a bytes from the stream.
0610: *
0611: * @param bytes the byte array to fill
0612: * @param offset the start position in the byte array
0613: * @param len the number of bytes to read
0614: * @return the number of bytes read or -1 if at end of file
0615: * @throws IOException if an I/O error occurs
0616: */
0617: public int read(byte[] bytes, int offset, int len)
0618: throws IOException {
0619: int b = BlobBuffer.this .read(readPtr, bytes, offset, len);
0620: if (b > 0) {
0621: readPtr += b;
0622: }
0623: return b;
0624: }
0625:
0626: /**
0627: * Closes the output stream.
0628: *
0629: * @throws IOException if an I/O error occurs
0630: */
0631: public void close() throws IOException {
0632: if (open) {
0633: BlobBuffer.this .close();
0634: open = false;
0635: }
0636: }
0637: }
0638:
0639: /**
0640: * A Big Endian Unicode <code>InputStream</code> over the CLOB buffer.
0641: */
0642: private class UnicodeInputStream extends InputStream {
0643: private int readPtr;
0644: private boolean open;
0645:
0646: /**
0647: * Costructs an InputStream object over the BLOB buffer.
0648: *
0649: * @param pos the starting position (from 0)
0650: * @throws IOException if an I/O error occurs
0651: */
0652: public UnicodeInputStream(long pos) throws IOException {
0653: BlobBuffer.this .open();
0654: open = true;
0655: readPtr = (int) pos;
0656: }
0657:
0658: /**
0659: * Ensures underlying BLOB file can be closed even if user does not
0660: * close this stream.
0661: */
0662: protected void finalize() throws Throwable {
0663: if (open) {
0664: try {
0665: close();
0666: } catch (IOException e) {
0667: // Ignore closing anyway
0668: } finally {
0669: super .finalize();
0670: }
0671: }
0672: }
0673:
0674: /**
0675: * Returns the number of bytes available to read.
0676: *
0677: * @throws IOException if an I/O error occurs
0678: */
0679: public int available() throws IOException {
0680: return (int) BlobBuffer.this .getLength() - readPtr;
0681: }
0682:
0683: /**
0684: * Reads the next byte from the stream.
0685: *
0686: * @return the next byte as an <code>int</code> or -1 if at EOF
0687: * @throws IOException if an I/O error occurs
0688: */
0689: public int read() throws IOException {
0690: //
0691: // The XOR of 1 with the readPtr forces the bytes to be returned
0692: // in big endian order.
0693: //
0694: int b = BlobBuffer.this .read(readPtr ^ 1);
0695: if (b >= 0) {
0696: readPtr++;
0697: }
0698: return b;
0699: }
0700:
0701: /**
0702: * Close the output stream.
0703: *
0704: * @throws IOException if an I/O error occurs
0705: */
0706: public void close() throws IOException {
0707: if (open) {
0708: BlobBuffer.this .close();
0709: open = false;
0710: }
0711: }
0712: }
0713:
0714: /**
0715: * An ASCII <code>InputStream</code> over the CLOB buffer.
0716: * <p/>
0717: * This class interprets ASCII as anything which has a value below 0x80.
0718: * This is more rigid than other drivers which allow any character below
0719: * 0x100 to be converted to returned. The more relaxed coding is useful
0720: * when dealing with most single byte character sets and if this behaviour
0721: * is desired, comment out the line indicated in the read method.
0722: */
0723: private class AsciiInputStream extends InputStream {
0724: private int readPtr;
0725: private boolean open;
0726:
0727: /**
0728: * Costructs an InputStream object over the BLOB buffer.
0729: *
0730: * @param pos the starting position (from 0)
0731: * @throws IOException if an I/O error occurs
0732: */
0733: public AsciiInputStream(long pos) throws IOException {
0734: BlobBuffer.this .open();
0735: open = true;
0736: readPtr = (int) pos;
0737: }
0738:
0739: /**
0740: * Ensures underlying BLOB file can be closed even if user does not
0741: * close this stream.
0742: */
0743: protected void finalize() throws Throwable {
0744: if (open) {
0745: try {
0746: close();
0747: } catch (IOException e) {
0748: // Ignore closing anyway
0749: } finally {
0750: super .finalize();
0751: }
0752: }
0753: }
0754:
0755: /**
0756: * Returns the number of bytes available to read.
0757: *
0758: * @throws IOException if an I/O error occurs
0759: */
0760: public int available() throws IOException {
0761: return ((int) BlobBuffer.this .getLength() - readPtr) / 2;
0762: }
0763:
0764: /**
0765: * Read the next byte from the stream.
0766: *
0767: * @return the next byte as an <code>int</code> or -1 if at EOF
0768: * @throws IOException if an I/O error occurs
0769: */
0770: public int read() throws IOException {
0771: int b1 = BlobBuffer.this .read(readPtr);
0772: if (b1 >= 0) {
0773: readPtr++;
0774: int b2 = BlobBuffer.this .read(readPtr);
0775: if (b2 >= 0) {
0776: readPtr++;
0777: if (b2 != 0 || b1 > 0x7F // Comment out this line for a more
0778: // permissive interpretation of 'ASCII'.
0779: ) {
0780: b1 = '?'; // Not ASCII set to '?'
0781: }
0782: return b1;
0783: }
0784: }
0785: return -1;
0786: }
0787:
0788: /**
0789: * Closes the output stream.
0790: *
0791: * @throws IOException if an I/O error occurs
0792: */
0793: public void close() throws IOException {
0794: if (open) {
0795: BlobBuffer.this .close();
0796: open = false;
0797: }
0798: }
0799: }
0800:
0801: /**
0802: * Implements an <code>OutputStream</code> for BLOB data.
0803: */
0804: private class BlobOutputStream extends OutputStream {
0805: private int writePtr;
0806: private boolean open;
0807:
0808: /**
0809: * Costructs an OutputStream object over the BLOB buffer.
0810: *
0811: * @param pos the starting position (from 0)
0812: * @throws IOException if an I/O error occurs
0813: */
0814: BlobOutputStream(long pos) throws IOException {
0815: BlobBuffer.this .open();
0816: open = true;
0817: writePtr = (int) pos;
0818: }
0819:
0820: /**
0821: * Ensures underlying BLOB file can be closed even if user does not
0822: * close this stream.
0823: */
0824: protected void finalize() throws Throwable {
0825: if (open) {
0826: try {
0827: close();
0828: } catch (IOException e) {
0829: // Ignore closing anyway
0830: } finally {
0831: super .finalize();
0832: }
0833: }
0834: }
0835:
0836: /**
0837: * Write a byte to the BLOB buffer.
0838: *
0839: * @param b the byte value to write
0840: * @throws IOException if an I/O error occurs
0841: */
0842: public void write(int b) throws IOException {
0843: BlobBuffer.this .write(writePtr++, b);
0844: }
0845:
0846: /**
0847: * Write bytes to the BLOB buffer.
0848: *
0849: * @param bytes the byte array value to write
0850: * @param offset the start position in the byte array
0851: * @param len the number of bytes to write
0852: * @throws IOException if an I/O error occurs
0853: */
0854: public void write(byte[] bytes, int offset, int len)
0855: throws IOException {
0856: BlobBuffer.this .write(writePtr, bytes, offset, len);
0857: writePtr += len;
0858: }
0859:
0860: /**
0861: * Close the output stream.
0862: *
0863: * @throws IOException if an I/O error occurs
0864: */
0865: public void close() throws IOException {
0866: if (open) {
0867: BlobBuffer.this .close();
0868: open = false;
0869: }
0870: }
0871: }
0872:
0873: /**
0874: * Implements an ASCII <code>OutputStream</code> for CLOB data.
0875: */
0876: private class AsciiOutputStream extends OutputStream {
0877: private int writePtr;
0878: private boolean open;
0879:
0880: /**
0881: * Costructs an ASCII <code>OutputStream</code> object over the BLOB
0882: * buffer.
0883: *
0884: * @param pos the starting position (from 0)
0885: * @throws IOException if an I/O error occurs
0886: */
0887: AsciiOutputStream(long pos) throws IOException {
0888: BlobBuffer.this .open();
0889: open = true;
0890: writePtr = (int) pos;
0891: }
0892:
0893: /**
0894: * Ensures underlying BLOB file can be closed even if user does not
0895: * close this stream.
0896: */
0897: protected void finalize() throws Throwable {
0898: if (open) {
0899: try {
0900: close();
0901: } catch (IOException e) {
0902: // Ignore closing anyway
0903: } finally {
0904: super .finalize();
0905: }
0906: }
0907: }
0908:
0909: /**
0910: * Writes a byte to the BLOB buffer.
0911: *
0912: * @param b the byte value to write
0913: * @throws IOException if an I/O error occurs
0914: */
0915: public void write(int b) throws IOException {
0916: BlobBuffer.this .write(writePtr++, b);
0917: BlobBuffer.this .write(writePtr++, 0);
0918: }
0919:
0920: /**
0921: * Closes the output stream.
0922: *
0923: * @throws IOException if an I/O error occurs
0924: */
0925: public void close() throws IOException {
0926: if (open) {
0927: BlobBuffer.this .close();
0928: open = false;
0929: }
0930: }
0931: }
0932:
0933: //
0934: // ---- Support methods for CLOB/BLOB ----
0935: //
0936:
0937: /**
0938: * Returns the BLOB data as a byte array.
0939: *
0940: * @param pos the start position in the BLOB buffer (from 1)
0941: * @param len the number of bytes to copy
0942: * @return the requested data as a <code>byte[]</code>
0943: */
0944: public byte[] getBytes(long pos, int len) throws SQLException {
0945: pos--;
0946: if (pos < 0) {
0947: throw new SQLException(Messages
0948: .get("error.blobclob.badpos"), "HY090");
0949: }
0950: if (pos > this .length) {
0951: throw new SQLException(Messages
0952: .get("error.blobclob.badposlen"), "HY090");
0953: }
0954: if (len < 0) {
0955: throw new SQLException(Messages
0956: .get("error.blobclob.badlen"), "HY090");
0957: }
0958: if (pos + len > this .length) {
0959: // Don't throw an exception, just return as much data as available
0960: len = (int) (this .length - pos);
0961: }
0962: try {
0963: // Should not do this. It could cause trouble.
0964: // if (pos == 0 && len == buffer.length && blobFile == null) {
0965: // // There is no file and we do not need a subset of the data.
0966: // // We should copy the buffer as the user may modify its
0967: // // contents but this would be wasteful in most cases.
0968: // return buffer;
0969: // }
0970: // We do need a subset or we are reading from the file
0971: byte[] data = new byte[len];
0972: if (blobFile == null) {
0973: // Just copy subset from memory buffer
0974: System.arraycopy(buffer, (int) (pos), data, 0, len);
0975: } else {
0976: // Copy data from disk buffer
0977: InputStream is = new BlobInputStream(pos);
0978: int bc = is.read(data);
0979: is.close();
0980: if (bc != data.length) {
0981: throw new IOException(
0982: "Unexpected EOF on BLOB data file bc=" + bc
0983: + " data.len=" + data.length);
0984: }
0985: }
0986: return data;
0987: } catch (IOException e) {
0988: throw new SQLException(Messages.get(
0989: "error.generic.ioerror", e.getMessage()), "HY000");
0990: }
0991: }
0992:
0993: /**
0994: * Retrieve the BLOB data as an <code>InputStream</code>.
0995: *
0996: * @param ascii true if an ASCII input stream should be returned
0997: * @return the <code>InputStream</code> built over the BLOB data
0998: * @throws SQLException if an error occurs
0999: */
1000: public InputStream getBinaryStream(boolean ascii)
1001: throws SQLException {
1002: try {
1003: if (ascii) {
1004: return new AsciiInputStream(0);
1005: } else {
1006: return new BlobInputStream(0);
1007: }
1008: } catch (IOException e) {
1009: throw new SQLException(Messages.get(
1010: "error.generic.ioerror", e.getMessage()), "HY000");
1011: }
1012: }
1013:
1014: /**
1015: * Retrieve the BLOB data as an Big Endian Unicode
1016: * <code>InputStream</code>.
1017: *
1018: * @return the <code>InputStream</code> built over the BLOB data
1019: * @throws SQLException if an error occurs
1020: */
1021: public InputStream getUnicodeStream() throws SQLException {
1022: try {
1023: return new UnicodeInputStream(0);
1024: } catch (IOException e) {
1025: throw new SQLException(Messages.get(
1026: "error.generic.ioerror", e.getMessage()), "HY000");
1027: }
1028: }
1029:
1030: /**
1031: * Creates an <code>OutputStream</code> that can be used to update the
1032: * BLOB.
1033: * <p/>
1034: * Given that we cannot know the final size of a BLOB created by the caller
1035: * of this method, we assume the worst and create a disk BLOB by default.
1036: *
1037: * @param pos the start position in the buffer (from 1)
1038: * @param ascii true if an ASCII output stream is required
1039: * @return the <code>OutputStream</code> to be used to update the BLOB
1040: * @throws SQLException if an error occurs
1041: */
1042: public OutputStream setBinaryStream(long pos, boolean ascii)
1043: throws SQLException {
1044: pos--;
1045: if (pos < 0) {
1046: throw new SQLException(Messages
1047: .get("error.blobclob.badpos"), "HY090");
1048: }
1049: if (pos > this .length) {
1050: throw new SQLException(Messages
1051: .get("error.blobclob.badposlen"), "HY090");
1052: }
1053: try {
1054: if (!isMemOnly && blobFile == null) {
1055: createBlobFile();
1056: }
1057: if (ascii) {
1058: return new AsciiOutputStream(pos);
1059: } else {
1060: return new BlobOutputStream(pos);
1061: }
1062: } catch (IOException e) {
1063: throw new SQLException(Messages.get(
1064: "error.generic.ioerror", e.getMessage()), "HY000");
1065: }
1066: }
1067:
1068: /**
1069: * Sets the content of the BLOB to the supplied byte array value.
1070: * <p/>
1071: * If the following conditions are met:
1072: * <ol>
1073: * <li>The start position is 1
1074: * <li>The existing BLOB length is smaller or the same as the length of
1075: * the new data
1076: * <li>The new data length does not exceed the in memory limit
1077: * </ol>
1078: * then the new data is buffered entirely in memory, otherwise a disk file
1079: * is created.
1080: *
1081: * @param pos the start position in the buffer (from 1)
1082: * @param bytes the byte array containing the data to copy
1083: * @param offset the start position in the byte array (from 0)
1084: * @param len the number of bytes to copy
1085: * @param copy true if a local copy of the byte array is required
1086: * @return the number of bytes copied
1087: * @throws SQLException if an error occurs
1088: */
1089: public int setBytes(long pos, byte[] bytes, int offset, int len,
1090: boolean copy) throws SQLException {
1091: pos--;
1092: if (pos < 0) {
1093: throw new SQLException(Messages
1094: .get("error.blobclob.badpos"), "HY090");
1095: }
1096: if (pos > this .length) {
1097: throw new SQLException(Messages
1098: .get("error.blobclob.badposlen"), "HY090");
1099: }
1100: if (bytes == null) {
1101: throw new SQLException(
1102: Messages.get("error.blob.bytesnull"), "HY009");
1103: }
1104: if (offset < 0 || offset > bytes.length) {
1105: throw new SQLException(Messages
1106: .get("error.blobclob.badoffset"), "HY090");
1107: }
1108: if (len < 0 || pos + len > (long) Integer.MAX_VALUE
1109: || offset + len > bytes.length) {
1110: throw new SQLException(Messages
1111: .get("error.blobclob.badlen"), "HY090");
1112: }
1113: //
1114: // If there is no disk file and this data will replace the
1115: // existing contents of the BLOB then just copy byte data to
1116: // a new buffer array if the size is small enough.
1117: //
1118: if (blobFile == null && pos == 0 && len >= this .length
1119: && len <= maxMemSize) {
1120: if (copy) {
1121: buffer = new byte[len];
1122: System.arraycopy(bytes, offset, buffer, 0, len);
1123: } else {
1124: // A copy is not always required
1125: buffer = bytes;
1126: }
1127: length = len;
1128: return len;
1129: }
1130: try {
1131: //
1132: // OK we will now try and create a BLOB file as this
1133: // is a more complex update.
1134: //
1135: if (!isMemOnly && blobFile == null) {
1136: createBlobFile();
1137: }
1138: //
1139: // Open the BLOB file
1140: //
1141: open();
1142: int ptr = (int) pos;
1143: write(ptr, bytes, offset, len);
1144: close();
1145: return len;
1146: } catch (IOException e) {
1147: throw new SQLException(Messages.get(
1148: "error.generic.ioerror", e.getMessage()), "HY000");
1149: }
1150: }
1151:
1152: /**
1153: * Retrieves the length of this BLOB buffer in bytes.
1154: *
1155: * @return the length of the BLOB data in bytes
1156: */
1157: public long getLength() {
1158: return this .length;
1159: }
1160:
1161: /**
1162: * Retrieves the length of the BLOB buffer (in memory version only).
1163: *
1164: * @param length the length of the valid data in the buffer
1165: */
1166: public void setLength(long length) {
1167: this .length = (int) length;
1168: }
1169:
1170: /**
1171: * Truncates the BLOB buffer to the specified size.
1172: *
1173: * @param len the required length
1174: * @throws SQLException if an error occurs
1175: */
1176: public void truncate(long len) throws SQLException {
1177: if (len < 0) {
1178: throw new SQLException(Messages
1179: .get("error.blobclob.badlen"), "HY090");
1180: }
1181: if (len > this .length) {
1182: throw new SQLException(Messages
1183: .get("error.blobclob.lentoolong"), "HY090");
1184: }
1185:
1186: length = (int) len;
1187: if (len == 0) {
1188: try {
1189: // Try to discard and delete work file
1190: // Any open input streams will get EOF
1191: // open write streams will probably fail.
1192: if (blobFile != null) {
1193: if (raFile != null) {
1194: raFile.close();
1195: }
1196: blobFile.delete();
1197: }
1198: } catch (IOException e) {
1199: throw new SQLException(Messages.get(
1200: "error.generic.ioerror", e.getMessage()),
1201: "HY000");
1202: } finally {
1203: buffer = EMPTY_BUFFER;
1204: blobFile = null;
1205: raFile = null;
1206: openCount = 0;
1207: currentPage = INVALID_PAGE;
1208: }
1209: }
1210: }
1211:
1212: /**
1213: * Provides support for pattern searching methods.
1214: *
1215: * @param pattern the byte array containg the search pattern
1216: * @param start the start position in the BLOB (from 1)
1217: * @return the <code>int</code> start index for the pattern (from 1) or -1
1218: * if the pattern is not found.
1219: * @throws SQLException if an error occurs
1220: */
1221: public int position(byte[] pattern, long start) throws SQLException {
1222: try {
1223: start--;
1224: if (start < 0) {
1225: throw new SQLException(Messages
1226: .get("error.blobclob.badpos"), "HY090");
1227: }
1228: if (start >= this .length) {
1229: throw new SQLException(Messages
1230: .get("error.blobclob.badposlen"), "HY090");
1231: }
1232: if (pattern == null) {
1233: throw new SQLException(Messages
1234: .get("error.blob.badpattern"), "HY009");
1235: }
1236: if (pattern.length == 0 || length == 0
1237: || pattern.length > length) {
1238: // Impossible for there to be a match
1239: return -1;
1240: }
1241: // FIXME Implement a better (O(n)) search algorithm
1242: int limit = (int) length - pattern.length;
1243: if (blobFile == null) {
1244: for (int i = (int) start; i <= limit; i++) {
1245: int p;
1246: for (p = 0; p < pattern.length
1247: && buffer[i + p] == pattern[p]; p++)
1248: ;
1249: if (p == pattern.length) {
1250: return i + 1;
1251: }
1252: }
1253: } else {
1254: open();
1255: for (int i = (int) start; i <= limit; i++) {
1256: int p;
1257: for (p = 0; p < pattern.length
1258: && read(i + p) == (pattern[p] & 0xFF); p++)
1259: ;
1260: if (p == pattern.length) {
1261: close();
1262: return i + 1;
1263: }
1264: }
1265: close();
1266: }
1267: return -1;
1268: } catch (IOException e) {
1269: throw new SQLException(Messages.get(
1270: "error.generic.ioerror", e.getMessage()), "HY000");
1271: }
1272: }
1273: }
|