Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.hsqldb.jdbc.JDBCClobFile Maven / Gradle / Ivy
/* 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;
}
}
}