All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hsqldb.jdbc.JDBCClobFile Maven / Gradle / Ivy

There is a newer version: 2.7.4
Show newest version
/* Copyright (c) 2001-2024, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.jdbc;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.IllegalCharsetNameException;

import java.sql.Clob;
import java.sql.SQLException;

import java.util.Scanner;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.hsqldb.error.ErrorCode;
import org.hsqldb.jdbc.JDBCBlobFile.OutputStreamAdapter;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.FrameworkLogger;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.InOutUtil;
import org.hsqldb.lib.Iterator;
import org.hsqldb.lib.List;

/**
 * A client-side file-based implementation of Clob.
 *
 * 
 * 
*

HSQLDB-Specific Information:

* * Starting with 2.1, in addition to HSQLDB driver support for both client-side * in-memory and remote SQL CLOB data implementations, this class is provided * to expose efficient, relatively high-performance CLOB operations over client * accessible files.

* * Design Notes

* * Although it is possible to implement a transactional version of this class, * the present implementation directly propagates changes to the underlying * file such that changes become visible as soon as they are either * implicitly or explicitly flushed to disk. * *

* * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net) * @version 2.7.3 * @since HSQLDB 2.1 */ public class JDBCClobFile implements java.sql.Clob { private static final FrameworkLogger LOG = FrameworkLogger.getLog( JDBCClobFile.class); /** * Retrieves the number of characters * in the {@code CLOB} value * designated by this {@code Clob} object. * * @return length of the {@code CLOB} in characters * @throws SQLException if there is an error accessing the * length of the {@code CLOB} value * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.2 */ public long length() throws SQLException { checkClosed(); if (m_fixedWidthCharset) { return m_file.length() / m_maxCharWidth; } ReaderAdapter adapter = null; try { adapter = new ReaderAdapter(m_file, 0, Long.MAX_VALUE); final long length = adapter.skip(Long.MAX_VALUE); return length; } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } finally { closeSafely(adapter); } } /** * Retrieves a copy of the specified substring * in the {@code CLOB} value * designated by this {@code Clob} object. * The substring begins at position * {@code pos} and has up to {@code length} consecutive * characters. * * @param pos the first character of the substring to be extracted. * The first character is at position 1. * @param length the number of consecutive characters to be copied; * the value for length must be 0 or greater * @return a {@code String} that is the specified substring in * the {@code CLOB} value designated by this {@code Clob} object * @throws SQLException if there is an error accessing the * {@code CLOB} value; if pos is less than 1 or length is * less than 0 * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.2 */ public String getSubString( final long pos, final int length) throws SQLException { Reader reader = null; CharArrayWriter writer; final int initialCapacity = Math.min( InOutUtil.DEFAULT_COPY_BUFFER_SIZE, length); try { reader = getCharacterStream(pos, length); writer = new CharArrayWriter(initialCapacity); InOutUtil.copy(reader, writer, length); return writer.toString(); } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } finally { closeSafely(reader); } } /** * Retrieves the {@code CLOB} value designated by this {@code Clob} * object as a {@code java.io.Reader} object (or as a stream of * characters). * * @return a {@code java.io.Reader} object containing the * {@code CLOB} data * @throws SQLException if there is an error accessing the * {@code CLOB} value * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @see #setCharacterStream * @since JDK 1.2 */ public Reader getCharacterStream() throws SQLException { return getCharacterStream(1, Long.MAX_VALUE); } /** * Retrieves the {@code CLOB} value designated by this {@code Clob} * object as an ASCII stream. * * @return a {@code java.io.InputStream} object containing the * {@code CLOB} data * @throws SQLException if there is an error accessing the * {@code CLOB} value * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @see #setAsciiStream * @since JDK 1.2 */ public InputStream getAsciiStream() throws SQLException { InputStream stream; final List streams = m_streams; try { stream = new JDBCBlobFile.InputStreamAdapter( m_file, 0, Long.MAX_VALUE) { private boolean closed; private InputStream self = this; public synchronized void close() throws IOException { if (closed) { return; } closed = true; try { super.close(); } finally { streams.remove(self); } } }; } catch (FileNotFoundException ex) { throw JDBCUtil.sqlException(ex); } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (SecurityException ex) { throw JDBCUtil.sqlException(ex); } catch (NullPointerException ex) { throw JDBCUtil.sqlException(ex); } catch (IllegalArgumentException ex) { throw JDBCUtil.sqlException(ex); } streams.add(stream); return stream; } /** * Retrieves the character position at which the specified char[] * {@code pattern} appears in the {@code CLOB} value * represented by this {@code Clob} object. The search * begins at position {@code start}. * * @param pattern the substring for which to search * @param start the position at which to begin searching; the first position * is 1 * @return the position at which the substring appears or -1 if it is not * present; the first position is 1 * @throws SQLException if there is an error accessing the * {@code CLOB} value or if pos is less than 1 * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method */ public long position( final char[] pattern, final long start) throws SQLException { if (start < 1) { throw JDBCUtil.outOfRangeArgument("start: " + start); } else if (pattern == null || pattern.length == 0) { return -1L; } long length = this.length(); if (start > length || pattern.length > length || start > length - pattern.length) { return -1; } return position0(new String(pattern), start); } /** * Retrieves the character position at which the specified * {@code pattern} appears in the SQL {@code CLOB} value * represented by this {@code Clob} object. The search begins at * position {@code start}. * * @param pattern for which to search * @param start position at which to begin searching; the first * position is 1 * @return the position at which the substring appears or -1 if it is not * present; the first position is 1 * @throws SQLException if there is an error accessing the * {@code CLOB} value or if start is less than 1 */ private long position0( final String pattern, final long start) throws SQLException { Pattern literal; try { literal = Pattern.compile(Pattern.quote(pattern)); } catch (PatternSyntaxException ex) { throw JDBCUtil.sqlException(ex); } Reader reader = null; Scanner scanner = null; boolean gotReaderAndScanner = false; try { reader = new ReaderAdapter(m_file, start - 1, m_file.length()); scanner = new Scanner(reader); gotReaderAndScanner = true; } catch (FileNotFoundException ex) { throw JDBCUtil.sqlException(ex); } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (SecurityException ex) { throw JDBCUtil.sqlException(ex); } catch (NullPointerException ex) { throw JDBCUtil.sqlException(ex); } catch (IllegalArgumentException ex) { throw JDBCUtil.sqlException(ex); } finally { if (!gotReaderAndScanner) { closeSafely(scanner); closeSafely(reader); } } int position = -1; try { if (scanner.hasNext()) { final boolean found = (null != scanner.findWithinHorizon( literal, 0)); if (found) { MatchResult match = scanner.match(); position = match.start() + 1; } } } finally { closeSafely(scanner); closeSafely(reader); } return position; } /** * Retrieves the character position at which the specified substring * {@code searchstr} appears in the SQL {@code CLOB} value * represented by this {@code Clob} object. The search * begins at position {@code start}. * * @param searchstr the substring for which to search * @param start the position at which to begin searching; the first position * is 1 * @return the position at which the substring appears or -1 if it is not * present; the first position is 1 * @throws SQLException if there is an error accessing the * {@code CLOB} value or if pos is less than 1 * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.2 */ public long position(String searchstr, long start) throws SQLException { if (start < 1) { throw JDBCUtil.outOfRangeArgument("start: " + start); } else if (searchstr == null || searchstr.isEmpty()) { return -1L; } final long length = this.length(); final int searchstrLength = searchstr.length(); if (start > length || searchstrLength > length || start > length - searchstrLength) { return -1; } return position0(searchstr, start); } /** * Retrieves the character position at which the specified * {@code Clob} object {@code searchstr} appears in this * {@code Clob} object. The search begins at position * {@code start}. * * @param pattern the {@code Clob} object for which to search * @param start the position at which to begin searching; the first * position is 1 * @return the position at which the {@code Clob} object appears * or -1 if it is not present; the first position is 1 * @throws SQLException if there is an error accessing the * {@code CLOB} value or if start is less than 1 * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.2 */ public long position( final Clob pattern, final long start) throws SQLException { long patternLength; if (start < 1) { throw JDBCUtil.outOfRangeArgument("start: " + start); } else if ((patternLength = pattern == null ? 0 : pattern.length()) == 0) { return -1L; } else if (patternLength > Integer.MAX_VALUE) { throw JDBCUtil.outOfRangeArgument( "pattern.length(): " + patternLength); } long length = this.length(); if (start > length || patternLength > length || start > length - patternLength) { return -1; } String stringPattern; if (pattern instanceof JDBCClob) { stringPattern = ((JDBCClob) pattern).getData(); } else { Reader reader = null; StringWriter writer = new StringWriter(); try { reader = pattern.getCharacterStream(); InOutUtil.copy(reader, writer, patternLength); } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (Throwable ex) { throw JDBCUtil.sqlException(ex); } finally { closeSafely(reader); } stringPattern = writer.toString(); } return position0(stringPattern, start); } //---------------------------- jdbc 3.0 ----------------------------------- /** * Writes the given Java {@code String} to the {@code CLOB} * value that this {@code Clob} object designates at the position * {@code pos}. The string will overwrite the existing characters * in the {@code Clob} object starting at the position * {@code pos}. If the end of the {@code Clob} value is reached * while writing the given string, then the length of the {@code Clob} * value will be increased to accommodate the extra characters. *

* Note: If the value specified for {@code pos} * is greater than the length+1 of the {@code CLOB} value then the * behavior is undefined. Some JDBC drivers may throw a * {@code SQLException} while other drivers may support this * operation. * * @param pos the position at which to start writing to the {@code CLOB} * value that this {@code Clob} object represents; * The first position is 1 * @param str the string to be written to the {@code CLOB} * value that this {@code Clob} designates * @return the number of characters written * @throws SQLException if there is an error accessing the * {@code CLOB} value or if pos is less than 1 * * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.4 */ public int setString(final long pos, final String str) throws SQLException { return setString( pos, str, 0, str == null ? 0 : str.length()); } /** * Writes {@code len} characters of {@code str}, starting * at character {@code offset}, to the {@code CLOB} value * that this {@code Clob} represents. The string will overwrite the existing characters * in the {@code Clob} object starting at the position * {@code pos}. If the end of the {@code Clob} value is reached * while writing the given string, then the length of the {@code Clob} * value will be increased to accommodate the extra characters. *

* Note: If the value specified for {@code pos} * is greater than the length+1 of the {@code CLOB} value then the * behavior is undefined. Some JDBC drivers may throw a * {@code SQLException} while other drivers may support this * operation. * * @param pos the position at which to start writing to this * {@code CLOB} object; The first position is 1 * @param str the string to be written to the {@code CLOB} * value that this {@code Clob} object represents * @param offset the offset into {@code str} to start reading * the characters to be written * @param len the number of characters to be written * @return the number of characters written * @throws SQLException if there is an error accessing the * {@code CLOB} value or if pos is less than 1 * * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.4 */ public int setString( final long pos, final String str, final int offset, final int len) throws SQLException { checkClosed(); if (str == null) { throw JDBCUtil.nullArgument("str"); } final int strlen = str.length(); if (offset < 0 || offset > strlen) { throw JDBCUtil.outOfRangeArgument("offset: " + offset); } if (len < 0 || len > strlen - offset) { throw JDBCUtil.outOfRangeArgument("len: " + len); } if (pos < 1L) { throw JDBCUtil.outOfRangeArgument("pos: " + pos); } long oldLength = this.length(); if (pos > oldLength + 1) { fillSpace(oldLength + 1, pos); } Writer writer = null; try { writer = setCharacterStream(pos); writer.write(str, offset, len); } catch (SQLException ex) { throw ex; } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (Throwable ex) { throw JDBCUtil.sqlException(ex); } finally { closeSafely(writer); } return len; } /** * * @param startPos inclusive * @param endPos exclusive * @throws SQLException exception */ private void fillSpace( final long startPos, long endPos) throws SQLException { Writer writer = null; try { writer = setCharacterStream(startPos); for (long i = endPos - startPos; i >= 0; i--) { writer.append(' '); } } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (Throwable ex) { throw JDBCUtil.sqlException(ex); } finally { closeSafely(writer); } } /** * Retrieves a stream to be used to write Ascii characters to the * {@code CLOB} value that this {@code Clob} object represents, * starting at position {@code pos}. Characters written to the stream * will overwrite the existing characters * in the {@code Clob} object starting at the position * {@code pos}. If the end of the {@code Clob} value is reached * while writing characters to the stream, then the length of the {@code Clob} * value will be increased to accommodate the extra characters. *

* Note: If the value specified for {@code pos} * is greater than the length+1 of the {@code CLOB} value then the * behavior is undefined. Some JDBC drivers may throw a * {@code SQLException} while other drivers may support this * operation. * * @param pos the position at which to start writing to this * {@code CLOB} object; The first position is 1 * @return the stream to which ASCII encoded characters can be written * @throws SQLException if there is an error accessing the * {@code CLOB} value or if pos is less than 1 * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @see #getAsciiStream * * @since JDK 1.4 */ public OutputStream setAsciiStream(long pos) throws SQLException { if (pos < 1) { throw JDBCUtil.invalidArgument("pos: " + pos); } checkClosed(); createFile(); long thisLength = this.length(); if (pos > thisLength + 1) { this.fillSpace(thisLength + 1, pos); } OutputStream stream; try { stream = new JDBCBlobFile.OutputStreamAdapter(m_file, pos - 1) { public void close() throws IOException { try { super.close(); } finally { m_streams.remove(this); } } }; } catch (FileNotFoundException ex) { throw JDBCUtil.sqlException(ex); } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (IllegalArgumentException ex) { throw JDBCUtil.sqlException(ex); } catch (NullPointerException ex) { throw JDBCUtil.sqlException(ex); } catch (SecurityException ex) { throw JDBCUtil.sqlException(ex); } m_streams.add(stream); return stream; } /** * Retrieves a stream to be used to write a stream of Unicode characters * to the {@code CLOB} value that this {@code Clob} object * represents, at position {@code pos}. Characters written to the stream * will overwrite the existing characters * in the {@code Clob} object starting at the position * {@code pos}. If the end of the {@code Clob} value is reached * while writing characters to the stream, then the length of the {@code Clob} * value will be increased to accommodate the extra characters. *

* Note: If the value specified for {@code pos} * is greater than the length+1 of the {@code CLOB} value then the * behavior is undefined. Some JDBC drivers may throw a * {@code SQLException} while other drivers may support this * operation. * * *

*

HSQLDB-Specific Information:

* * When the value specified for {@code pos} is greater then the * length+1, an {@code SQLException} is thrown. *
* * @param pos the position at which to start writing to the * {@code CLOB} value; The first position is 1 * * @return a stream to which Unicode encoded characters can be written * @throws SQLException if there is an error accessing the * {@code CLOB} value or if pos is less than 1 * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @see #getCharacterStream * * @since JDK 1.4 */ public Writer setCharacterStream(final long pos) throws SQLException { if (pos < 1) { throw JDBCUtil.invalidArgument("pos: " + pos); } checkClosed(); createFile(); long thisLength = this.length(); if (pos > thisLength + 1) { this.fillSpace(thisLength + 1, pos); } Writer writer; WriterAdapter adapter; try { adapter = new WriterAdapter(m_file, pos - 1) { public void close() throws IOException { try { super.close(); } finally { m_streams.remove(this); } } }; writer = new BufferedWriter(adapter); } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (Throwable ex) { throw JDBCUtil.sqlException(ex); } m_streams.add(adapter); return writer; } /** * Truncates the {@code CLOB} value that this {@code Clob} * designates to have a length of {@code len} * characters. *

* Note: If the value specified for {@code pos} * is greater than the length+1 of the {@code CLOB} value then the * behavior is undefined. Some JDBC drivers may throw a * {@code SQLException} while other drivers may support this * operation. * * @param len the length, in characters, to which the {@code CLOB} value * should be truncated * @throws SQLException if there is an error accessing the * {@code CLOB} value or if len is less than 0 * * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.4 */ public void truncate(long len) throws SQLException { if (len < 0) { throw JDBCUtil.invalidArgument("len: " + len); } checkClosed(); ReaderAdapter adapter = null; RandomAccessFile randomAccessFile = null; long filePointer; try { adapter = new ReaderAdapter(m_file, len, Long.MAX_VALUE); filePointer = adapter.getFilePointer(); adapter.close(); randomAccessFile = new RandomAccessFile(m_file, "rw"); randomAccessFile.setLength(filePointer); } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (Throwable ex) { throw JDBCUtil.sqlException(ex); } finally { closeSafely(adapter); closeSafely(randomAccessFile); } } /** * This method frees the {@code Clob} object and releases the resources * that it holds. The object is invalid once the {@code free} method * is called. *

* After {@code free} has been called, any attempt to invoke a * method other than {@code free} will result in a {@code SQLException} * being thrown. If {@code free} is called multiple times, the subsequent * calls to {@code free} are treated as a no-op. * * @throws SQLException if an error occurs releasing * the Clob's resources * * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since JDK 1.4 */ public synchronized void free() throws SQLException { if (m_closed) { return; } m_closed = true; final List streams = m_streams; m_streams = null; Iterator itr = streams.iterator(); while (itr.hasNext()) { final Object stream = itr.next(); closeSafely(stream); } if (m_deleteOnFree) { try { m_file.delete(); } catch (SecurityException e) {} } } /** * Returns a {@code Reader} object that contains a partial {@code Clob} value, starting * with the character specified by pos, which is length characters in length. * * @param pos the offset to the first character of the partial value to * be retrieved. The first character in the Clob is at position 1. * @param length the length in characters of the partial value to be retrieved. * @return {@code Reader} through which the partial {@code Clob} value can be read. * @throws SQLException if pos is less than 1 or if pos is greater than the number of * characters in the {@code Clob} or if pos + length is greater than the number of * characters in the {@code Clob} * * @throws java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * @since 1.6 */ public Reader getCharacterStream( long pos, long length) throws SQLException { if (pos < 1) { throw JDBCUtil.outOfRangeArgument("pos: " + pos); } if (length < 0) { throw JDBCUtil.outOfRangeArgument("length: " + length); } Reader reader; final List streams = m_streams; try { reader = new ReaderAdapter(m_file, pos - 1, length) { public void close() throws IOException { try { super.close(); } finally { streams.remove(this); } } }; } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (Throwable ex) { throw JDBCUtil.sqlException(ex); } streams.add(reader); return reader; } /** * Retrieves the canonical {@code File} object denoting the file that * backs this CLOB. * * @return the file that backs this CLOB. */ public File getFile() { return m_file; } /** * * @return the name of the character encoding used to read and write * character data in the underlying files, as well as to determine * the character length and character offsets into the underlying * file */ public String getEncoding() { return m_encoding; } /** * Retrieves whether an attempt to delete the backing file * is made in response to invocation of {@link #free()}. * * @return true if backing file deletion is attempted; otherwise false. */ public boolean isDeleteOnFree() { return m_deleteOnFree; } /** * Assigns whether an attempt to delete the backing file * is made in response to invocation of {@link #free()}. * * @param deleteOnFree the new value to assign */ public void setDeleteOnFree(boolean deleteOnFree) { m_deleteOnFree = deleteOnFree; } //-------------------------------------------------------------------------- // Internal Implementation //-------------------------------------------------------------------------- public static final String TEMP_FILE_PREFIX = "hsql_jdbc_clob_file_"; public static final String TEMP_FILE_SUFFIX = ".tmp"; // private final File m_file; // private boolean m_closed; private boolean m_deleteOnFree; private String m_encoding; private Charset m_charset; private CharsetEncoder m_encoder; private boolean m_fixedWidthCharset; private int m_maxCharWidth; private List m_streams = new HsqlArrayList<>(); /** * Convenience constructor for {@link * #JDBCClobFile(java.lang.String) * JDBCClobFile((String)null)}. * * @throws SQLException if the platform encoding is unsupported, * the temp file cannot be created or some other * error occurs that prevents the construction of a * valid instance of this class. */ public JDBCClobFile() throws SQLException { this((String) null); } /** * Constructs a new JDBCClobFile instance backed by a File object * created by File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX), * using the given encoding to read and write file content. * * @param encoding the name of the character encoding used to read and write * character data in the underlying file, as well as to determine * the character length of and character offsets into the underlying * file. Specify null to denote the platform encoding. * * @throws SQLException if the given encoding is unsupported, * the backing temp file could not be created or if a security * manager exists and its {@code {@link * java.lang.SecurityManager#checkWrite(java.lang.String)}} * method does not allow a file to be created. */ public JDBCClobFile(String encoding) throws SQLException { try { setEncoding(encoding); m_file = File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX); m_deleteOnFree = true; } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (SecurityException se) { throw JDBCUtil.sqlException(se); } } /** * Convenience constructor for {@link * #JDBCClobFile(java.io.File, java.lang.String) * JDBCClobFile(file,null)}. * * @param file that is to back the new CLOB instance. * * @throws SQLException if an I/O error occurs, which is possible because the * construction of the canonical pathname may require * file-system queries; a required system property value * cannot be accessed; a security manager exists and its * {@code {@link java.lang.SecurityManager#checkRead}} * method denies read access to the file */ public JDBCClobFile(File file) throws SQLException { this(file, null); } /** * Constructs a new JDBCClobFile instance backed by the given File object * using the given encoding to read and write file content. * * @param file that is to back the new CLOB instance. * @param encoding the name of the character encoding used to read and write * character data in the underlying file, as well as to determine * the character length of and character offsets into the underlying * file. Specify null to denote the platform encoding. * * @throws SQLException if the given encoding is unsupported; * an I/O error occurs, which is possible because the * construction of the canonical pathname may require * file-system queries; a required system property value * cannot be accessed; a security manager exists and its * {@code {@link java.lang.SecurityManager#checkRead}} * method denies read access to the file */ public JDBCClobFile(File file, String encoding) throws SQLException { if (file == null) { throw JDBCUtil.nullArgument("file"); } try { setEncoding(encoding); m_file = file.getCanonicalFile(); checkIsFile( /*checkExists*/false); m_deleteOnFree = false; } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (SQLException ex) { throw JDBCUtil.sqlException(ex); } } protected final void setEncoding( final String encoding) throws UnsupportedEncodingException { final Charset charSet = charsetForName(encoding); final CharsetEncoder encoder = charSet.newEncoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); final float maxBytesPerChar = encoder.maxBytesPerChar(); final float averageBytesPerChar = encoder.averageBytesPerChar(); final boolean fixedWidthCharset = (maxBytesPerChar == Math.round( maxBytesPerChar)) && (maxBytesPerChar == averageBytesPerChar); // m_fixedWidthCharset = fixedWidthCharset; m_maxCharWidth = Math.round(maxBytesPerChar); m_charset = charSet; m_encoder = encoder; m_encoding = m_charset.name(); } protected static Charset charsetForName( final String charsetName) throws UnsupportedEncodingException { String csn = charsetName; if (csn == null) { csn = Charset.defaultCharset().name(); } try { if (Charset.isSupported(csn)) { return Charset.forName(csn); } } catch (IllegalCharsetNameException x) { LOG.warning(x.getMessage(), x); } throw new UnsupportedEncodingException(csn); } protected final void checkIsFile( final boolean checkExists) throws SQLException { boolean exists = false; boolean isFile = false; try { exists = m_file.exists(); } catch (Exception ex) { throw JDBCUtil.sqlException(ex); } if (exists) { try { isFile = m_file.isFile(); } catch (Exception ex) { throw JDBCUtil.sqlException(ex); } } if (exists) { if (!isFile) { throw JDBCUtil.invalidArgument("Is not a file: " + m_file); } } else if (checkExists) { throw JDBCUtil.invalidArgument("Does not exist: " + m_file); } } protected void checkClosed() throws SQLException { if (m_closed) { throw JDBCUtil.sqlException(ErrorCode.X_07501); } } protected void createFile() throws SQLException { try { if (!m_file.exists()) { FileUtil.getFileUtil().makeParentDirectories(m_file); m_file.createNewFile(); } } catch (IOException ex) { throw JDBCUtil.sqlException(ex); } catch (Throwable ex) { throw JDBCUtil.sqlException(ex); } checkIsFile( /*checkExists*/true); } // private static void closeSafely(final RandomAccessFile target) { if (target != null) { try { target.close(); } catch (IOException ignoredIoe) { LOG.info(ignoredIoe.getMessage(), ignoredIoe); } catch (Throwable ignoredRex) { LOG.info(ignoredRex.getMessage(), ignoredRex); } } } private static void closeSafely(final InputStream target) { if (target != null) { try { target.close(); } catch (IOException ignoredIoe) { LOG.info(ignoredIoe.getMessage(), ignoredIoe); } catch (Throwable ignoredRex) { LOG.info(ignoredRex.getMessage(), ignoredRex); } } } private static void closeSafely(final OutputStream target) { if (target != null) { try { target.close(); } catch (IOException ignoredIoe) { LOG.info(ignoredIoe.getMessage(), ignoredIoe); } catch (Throwable ignoredRex) { LOG.info(ignoredRex.getMessage(), ignoredRex); } } } private static void closeSafely(final Reader target) { if (target != null) { try { target.close(); } catch (IOException ignoredIoe) { LOG.info(ignoredIoe.getMessage(), ignoredIoe); } catch (Throwable ignoredRex) { LOG.info(ignoredRex.getMessage(), ignoredRex); } } } private static void closeSafely(final Writer target) { if (target != null) { try { target.close(); } catch (IOException ignoredIoe) { LOG.info(ignoredIoe.getMessage(), ignoredIoe); } catch (Throwable ignoredRex) { LOG.info(ignoredRex.getMessage(), ignoredRex); } } } private static void closeSafely(final Scanner target) { if (target != null) { try { target.close(); } catch (Throwable ignoredRex) { LOG.info(ignoredRex.getMessage(), ignoredRex); } } } private void closeSafely(final Object target) { if (target instanceof RandomAccessFile) { closeSafely((RandomAccessFile) target); } else if (target instanceof InputStream) { closeSafely((InputStream) target); } else if (target instanceof OutputStream) { closeSafely((OutputStream) target); } else if (target instanceof Reader) { closeSafely((Reader) target); } else if (target instanceof Writer) { closeSafely((Writer) target); } else if (target instanceof Scanner) { closeSafely((Scanner) target); } } // protected class WriterAdapter extends Writer { protected final Writer m_writer; protected WriterAdapter( final File file, final long pos) throws IOException, SecurityException, NullPointerException, IllegalArgumentException { if (file == null) { throw new NullPointerException("file"); } if (pos < 0) { throw new IllegalArgumentException("pos: " + pos); } ReaderAdapter reader = null; long filePointer; try { reader = new ReaderAdapter(file, pos, Long.MAX_VALUE); filePointer = reader.getFilePointer(); } finally { closeSafely(reader); } RandomAccessFile raf = null; boolean success = false; try { raf = new RandomAccessFile(file, "rw"); raf.seek(filePointer); success = true; } finally { if (!success) { closeSafely(raf); } } final OutputStreamAdapter osa = new OutputStreamAdapter(raf); m_writer = m_encoding == null ? new OutputStreamWriter(osa) : new OutputStreamWriter(osa, m_charset); } public void flush() throws IOException { m_writer.flush(); } public void close() throws IOException { m_writer.close(); } public void write(char[] cbuf, int off, int len) throws IOException { m_writer.write(cbuf, off, len); } } protected class ReaderAdapter extends Reader { // private static final int CHARBUFFER_CAPACTIY = 128; // private final Reader m_reader; private long m_remaining = Long.MAX_VALUE; private long m_filePointer; private ByteBuffer m_byteBuffer; private CharBuffer m_charBuffer; protected ReaderAdapter( final File file, final long pos, final long length) throws IOException, NullPointerException, IllegalArgumentException, SecurityException { if (file == null) { throw new NullPointerException("file"); } if (pos < 0) { throw new IllegalArgumentException("pos: " + pos); } if (length < 0) { throw new IllegalArgumentException("length: " + length); } long fileLength = file.length(); long maxCharsLength = m_fixedWidthCharset ? Math.round( fileLength / (double) m_maxCharWidth) : fileLength; if (maxCharsLength - pos < 0) { throw new IllegalArgumentException("pos: " + pos); } // if (!m_fixedWidthCharset) { final int charCapacity = CHARBUFFER_CAPACTIY; final int byteCapacity = charCapacity * m_maxCharWidth; m_charBuffer = CharBuffer.allocate(charCapacity); m_byteBuffer = ByteBuffer.allocate(byteCapacity); } FileInputStream fis = null; try { fis = new FileInputStream(file); final BufferedInputStream bis = new BufferedInputStream(fis); final InputStreamReader isr = new InputStreamReader( bis, m_charset); m_reader = isr; } catch (FileNotFoundException ex) { closeSafely(fis); throw ex; } catch (SecurityException ex) { closeSafely(fis); throw ex; } catch (NullPointerException ex) { closeSafely(fis); throw ex; } catch (RuntimeException ex) { closeSafely(fis); throw ex; } long skipped = this.skip(pos); if (skipped < pos) { throw new IllegalArgumentException("pos: " + pos); } // important - do not assign until *after* seek above. m_remaining = length; } public int read( final char[] cbuf, final int off, int len) throws IOException { final long l_remaining = m_remaining; if (l_remaining <= 0) { return -1; } else if (l_remaining < len) { len = (int) l_remaining; } int charsRead = m_reader.read(cbuf, off, len); if (charsRead == -1) { return -1; } else if (charsRead > l_remaining) { charsRead = (int) l_remaining; m_remaining = 0; } else { m_remaining -= charsRead; } int bytesRead; if (m_fixedWidthCharset) { bytesRead = (m_maxCharWidth * charsRead); } else { final boolean reallocate = (charsRead > m_charBuffer.capacity()); final CharBuffer cb = reallocate ? CharBuffer.allocate(charsRead) : m_charBuffer; final ByteBuffer bb = reallocate ? ByteBuffer.allocate( charsRead * m_maxCharWidth) : m_byteBuffer; // cb.clear(); bb.clear(); cb.put(cbuf, off, charsRead); cb.flip(); m_encoder.encode(cb, bb, /*endOfinput*/ true); bb.flip(); bytesRead = bb.limit(); if (reallocate) { m_byteBuffer = bb; m_charBuffer = cb; } } m_filePointer += bytesRead; return charsRead; } public void close() throws IOException { m_reader.close(); } public long getFilePointer() { return m_filePointer; } } }