changeset 209:94131372ad0b

Improved implementation of MonetClob and MonetBlob classes by adding checks on validity of input parameters of methods of those classes. This makes them more robust against incorrect usage by throwing an SQLException instead of a NullPointerException or other Exception. Also made these classes final, as we do not want anybody to subclass these classes.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 08 Mar 2018 18:16:05 +0100 (2018-03-08)
parents ae1d0d1c2f0f
children 2dbfc65d8e03
files src/main/java/nl/cwi/monetdb/jdbc/MonetBlob.java src/main/java/nl/cwi/monetdb/jdbc/MonetClob.java
diffstat 2 files changed, 165 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetBlob.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetBlob.java
@@ -16,16 +16,18 @@ import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
 
 /**
- * The MonetBlob class implements the {@link java.sql.Blob} interface.  Because
- * MonetDB/SQL currently has no support for streams, this class is a
+ * The MonetBlob class implements the {@link java.sql.Blob} interface.
+ *
+ * Because MonetDB/SQL currently has no support for streams, this class is a
  * shallow wrapper of a byte[].  It is more or less supplied to
  * enable an application that depends on it to run.  It may be obvious
  * that it is a real resource expensive workaround that contradicts the
  * benefits for a Blob: avoidance of huge resource consumption.
+ * <b>Use of this class is highly discouraged.</b>
  *
  * @author Fabian Groffen
  */
-public class MonetBlob implements Blob {
+public final class MonetBlob implements Blob {
 	private byte[] buf;
 
 	protected MonetBlob(byte[] data) {
@@ -45,7 +47,7 @@ public class MonetBlob implements Blob {
 	}
 
 	/* internal utility method */
-	private void checkBufIsNotNull() throws SQLException {
+	final private void checkBufIsNotNull() throws SQLException {
 		if (buf == null)
 			throw new SQLException("This MonetBlob has been freed", "M1M20");
 	}
@@ -63,8 +65,6 @@ public class MonetBlob implements Blob {
 	 *
 	 * @throws SQLException if an error occurs releasing the Blob's
 	 *         resources
-	 * @throws SQLFeatureNotSupportedException - if the JDBC driver does
-	 *         not support this method
 	 */
 	@Override
 	public void free() throws SQLException {
@@ -77,8 +77,6 @@ public class MonetBlob implements Blob {
 	 *
 	 * @return a stream containing the BLOB data
 	 * @throws SQLException if there is an error accessing the BLOB value
-	 * @throws SQLFeatureNotSupportedException if the JDBC driver does
-	 *         not support this method
 	 */
 	@Override
 	public InputStream getBinaryStream() throws SQLException {
@@ -100,21 +98,20 @@ public class MonetBlob implements Blob {
 	 * @throws SQLException if pos is less than 1 or if pos is
 	 *         greater than the number of bytes in the Blob or if pos +
 	 *         length is greater than the number of bytes in the Blob
-	 * @throws SQLFeatureNotSupportedException if the JDBC driver does
-	 *         not support this method
 	 */
 	@Override
 	public InputStream getBinaryStream(long pos, long length)
 		throws SQLException
 	{
 		checkBufIsNotNull();
-		if (pos < 1)
-			throw new SQLException("pos is less than 1", "M1M05");
-		if (pos - 1 > buf.length)
-			throw new SQLException("pos is greater than the number of bytes in the Blob", "M1M05");
-		if (pos - 1 + length > buf.length)
-			throw new SQLException("pos + length is greater than the number of bytes in the Blob", "M1M05");
-		return new ByteArrayInputStream(buf, (int)(pos - 1), (int)length);
+		if (pos < 1 || pos > buf.length) {
+			throw new SQLException("Invalid pos value: " + pos, "M1M05");
+		}
+		if (length < 0 || pos - 1 + length > buf.length) {
+			throw new SQLException("Invalid length value: " + length, "M1M05");
+		}
+
+		return new ByteArrayInputStream(buf, (int) pos - 1, (int) length);
 	}
 
 	/**
@@ -134,6 +131,13 @@ public class MonetBlob implements Blob {
 	@Override
 	public byte[] getBytes(long pos, int length) throws SQLException {
 		checkBufIsNotNull();
+		if (pos < 1 || pos > buf.length) {
+			throw new SQLException("Invalid pos value: " + pos, "M1M05");
+		}
+		if (length < 0 || pos - 1 + length > buf.length) {
+			throw new SQLException("Invalid length value: " + length, "M1M05");
+		}
+
 		try {
 			return java.util.Arrays.copyOfRange(buf, (int) pos - 1, (int) pos - 1 + length);
 		} catch (IndexOutOfBoundsException e) {
@@ -170,6 +174,9 @@ public class MonetBlob implements Blob {
 	 */
 	@Override
 	public long position(Blob pattern, long start) throws SQLException {
+		if (pattern == null) {
+			throw new SQLException("Missing pattern object", "M1M05");
+		}
 		return position(pattern.getBytes(1L, (int)pattern.length()), start);
 	}
 
@@ -188,14 +195,22 @@ public class MonetBlob implements Blob {
 	@Override
 	public long position(byte[] pattern, long start) throws SQLException {
 		checkBufIsNotNull();
+		if (pattern == null) {
+			throw new SQLException("Missing pattern object", "M1M05");
+		}
+		if (start < 1 || start > buf.length) {
+			throw new SQLException("Invalid start value: " + start, "M1M05");
+		}
 		try {
-			for (int i = (int)(start - 1); i < buf.length - pattern.length; i++) {
+			final int patternLength = pattern.length;
+			final int bufLength = buf.length;
+			for (int i = (int)(start - 1); i < bufLength - patternLength; i++) {
 				int j;
-				for (j = 0; j < pattern.length; j++) {
+				for (j = 0; j < patternLength; j++) {
 					if (buf[i + j] != pattern[j])
 						break;
 				}
-				if (j == pattern.length)
+				if (j == patternLength)
 					return i;
 			}
 		} catch (IndexOutOfBoundsException e) {
@@ -238,10 +253,13 @@ public class MonetBlob implements Blob {
 	 *        that this Blob object represents
 	 * @return the number of bytes written
 	 * @throws SQLException if there is an error accessing the
-	 *         BLOB value
+	 *         BLOB value or if pos is less than 1
 	 */
 	@Override
 	public int setBytes(long pos, byte[] bytes) throws SQLException {
+		if (bytes == null) {
+			throw new SQLException("Missing bytes[] object", "M1M05");
+		}
 		return setBytes(pos, bytes, 1, bytes.length);
 	}
 
@@ -261,13 +279,26 @@ public class MonetBlob implements Blob {
 	 *        from the array of bytes bytes
 	 * @return the number of bytes written
 	 * @throws SQLException if there is an error accessing the
-	 *         BLOB value
+	 *         BLOB value or if pos is less than 1
 	 */
 	@Override
 	public int setBytes(long pos, byte[] bytes, int offset, int len)
 		throws SQLException
 	{
 		checkBufIsNotNull();
+		if (bytes == null) {
+			throw new SQLException("Missing bytes[] object", "M1M05");
+		}
+		if (pos < 1 || pos > Integer.MAX_VALUE) {
+			throw new SQLException("Invalid pos value: " + pos, "M1M05");
+		}
+		if (len < 0 || pos + len > buf.length) {
+			throw new SQLException("Invalid len value: " + len, "M1M05");
+		}
+		if (offset < 0 || offset > bytes.length) {
+			throw new SQLException("Invalid offset value: " + offset, "M1M05");
+		}
+
 		try {
 			/* transactions? what are you talking about? */
 			for (int i = (int)pos; i < len; i++)
@@ -290,6 +321,9 @@ public class MonetBlob implements Blob {
 	@Override
 	public void truncate(long len) throws SQLException {
 		checkBufIsNotNull();
+		if (len < 0 || len > buf.length) {
+			throw new SQLException("Invalid len value: " + len, "M1M05");
+		}
 		if (buf.length > len) {
 			byte[] newbuf = new byte[(int)len];
 			for (int i = 0; i < len; i++)
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetClob.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetClob.java
@@ -18,8 +18,9 @@ import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
 
 /**
- * The MonetClob class implements the {@link java.sql.Clob} interface.  Because
- * MonetDB/SQL currently has no support for streams, this class is a
+ * The MonetClob class implements the {@link java.sql.Clob} interface.
+ *
+ * Because MonetDB/SQL currently has no support for streams, this class is a
  * shallow wrapper of a {@link StringBuilder}.  It is more or less supplied to
  * enable an application that depends on it to run.  It may be obvious
  * that it is a real resource expensive workaround that contradicts the
@@ -28,7 +29,7 @@ import java.sql.SQLFeatureNotSupportedEx
  *
  * @author Fabian Groffen
  */
-public class MonetClob implements Clob {
+public final class MonetClob implements Clob {
 
 	private StringBuilder buf;
 
@@ -37,7 +38,7 @@ public class MonetClob implements Clob {
 	}
 
 	/* internal utility method */
-	private void checkBufIsNotNull() throws SQLException {
+	final private void checkBufIsNotNull() throws SQLException {
 		if (buf == null)
 			throw new SQLException("This MonetClob has been freed", "M1M20");
 	}
@@ -64,6 +65,7 @@ public class MonetClob implements Clob {
 	 * ascii stream.
 	 *
 	 * @return a java.io.InputStream object containing the CLOB data
+	 * @throws SQLException - if there is an error accessing the CLOB value
 	 * @throws SQLFeatureNotSupportedException this JDBC driver does
 	 *         not support this method
 	 */
@@ -77,8 +79,7 @@ public class MonetClob implements Clob {
 	 * java.io.Reader object (or as a stream of characters).
 	 *
 	 * @return a java.io.Reader object containing the CLOB data
-	 * @throws SQLFeatureNotSupportedException this JDBC driver does
-	 *         not support this method
+	 * @throws SQLException - if there is an error accessing the CLOB value
 	 */
 	@Override
 	public Reader getCharacterStream() throws SQLException {
@@ -97,12 +98,19 @@ public class MonetClob implements Clob {
 	 * @param length the length in characters of the partial value to be
 	 *        retrieved.
 	 * @return Reader through which the partial Clob value can be read.
-	 * @throws SQLFeatureNotSupportedException this JDBC driver does
-	 *         not support this method
+	 * @throws SQLException - if pos is less than 1
+	 *         or if pos is greater than the number of characters in the Clob
+	 *         or if pos + length is greater than the number of characters in the Clob
 	 */
 	@Override
 	public Reader getCharacterStream(long pos, long length) throws SQLException {
 		checkBufIsNotNull();
+		if (pos < 1 || pos > buf.length()) {
+			throw new SQLException("Invalid pos value: " + pos, "M1M05");
+		}
+		if (length < 0 || pos -1 + length > buf.length()) {
+			throw new SQLException("Invalid length value: " + length, "M1M05");
+		}
 		return new StringReader(getSubString(pos, (int)length));
 	}
 
@@ -116,16 +124,24 @@ public class MonetClob implements Clob {
 	 * @param length the number of consecutive characters to be copied
 	 * @return a String that is the specified substring in the
 	 *         CLOB value designated by this Clob object
-	 * @throws SQLException if there is an error accessing the
-	 *         CLOB value
+	 * @throws SQLException - if pos is less than 1
+	 *         or if pos is greater than the number of characters in the Clob
+	 *         or if pos + length is greater than the number of characters in the Clob
+	 * @throws SQLException - if there is an error accessing the CLOB value
 	 */
 	@Override
 	public String getSubString(long pos, int length) throws SQLException {
 		checkBufIsNotNull();
+		if (pos < 1 || pos > buf.length()) {
+			throw new SQLException("Invalid pos value: " + pos, "M1M05");
+		}
+		if (length < 0 || pos -1 + length > buf.length()) {
+			throw new SQLException("Invalid length value: " + length, "M1M05");
+		}
 		try {
 			return buf.substring((int)(pos - 1), (int)(pos - 1 + length));
 		} catch (IndexOutOfBoundsException e) {
-			throw new SQLException(e.getMessage());
+			throw new SQLException(e.getMessage(), "M1M05");
 		}
 	}
 
@@ -153,12 +169,14 @@ public class MonetClob implements Clob {
 	 *        the first position is 1
 	 * @return the position at which the Clob object appears or
 	 *         -1 if it is not present; the first position is 1
-	 * @throws SQLException if there is an error accessing the
-	 *         CLOB value
+	 * @throws SQLException - if there is an error accessing the CLOB value
 	 */
 	@Override
 	public long position(Clob searchstr, long start) throws SQLException {
-		return position(searchstr.getSubString(1L, (int)(searchstr.length())), start);
+		if (searchstr == null) {
+			throw new SQLException("Missing searchstr object", "M1M05");
+		}
+		return position(searchstr.toString(), start);
 	}
 
 	/**
@@ -171,28 +189,67 @@ public class MonetClob implements Clob {
 	 *        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
-	 *         CLOB value
+	 * @throws SQLException - if there is an error accessing the CLOB value
 	 */
 	@Override
 	public long position(String searchstr, long start) throws SQLException {
 		checkBufIsNotNull();
+		if (searchstr == null) {
+			throw new SQLException("Missing searchstr object", "M1M05");
+		}
+		if (start < 1 || start > buf.length()) {
+			throw new SQLException("Invalid start value: " + start, "M1M05");
+		}
 		return (long)(buf.indexOf(searchstr, (int)(start - 1)));
 	}
 
+	/**
+	 * Retrieves a stream to be used to write Ascii characters to the CLOB value that this
+	 * Clob object represents, starting at position pos. Characters written to the stream
+	 * will overwrite the existing characters in the Clob object starting at the position pos.
+	 * If the end of the Clob value is reached while writing characters to the stream,
+	 * then the length of the Clob value will be increased to accomodate the extra characters.
+	 *
+	 * Note: If the value specified for pos is greater then the length+1 of the CLOB value
+	 * then the behavior is undefined. Some JDBC drivers may throw a SQLException while
+	 * other drivers may support this operation.
+	 *
+	 * @param pos - the position at which to start writing to this 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 CLOB value or if pos is less than 1
+	 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this method
+	 */
 	@Override
 	public OutputStream setAsciiStream(long pos) throws SQLException {
 		throw new SQLFeatureNotSupportedException("Method setAsciiStream(long pos) not supported", "0A000");
 	}
 
+	/**
+	 * Retrieves a stream to be used to write a stream of Unicode characters to the CLOB value that
+	 * this Clob object represents, starting at position pos. Characters written to the stream
+	 * will overwrite the existing characters in the Clob object starting at the position pos.
+	 * If the end of the Clob value is reached while writing characters to the stream,
+	 * then the length of the Clob value will be increased to accomodate the extra characters.
+	 *
+	 * Note: If the value specified for pos is greater then the length+1 of the CLOB value
+	 * then the behavior is undefined. Some JDBC drivers may throw a SQLException while
+	 * other drivers may support this operation.
+	 *
+	 * @param pos - the position at which to start writing to this CLOB object; The first position is 1
+	 * @return the stream to which Unicode encoded characters can be written
+	 * @throws SQLException - if there is an error accessing the CLOB value or if pos is less than 1
+	 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this method
+	 */
 	@Override
 	public Writer setCharacterStream(long pos) throws SQLException {
 		throw new SQLFeatureNotSupportedException("Method setCharacterStream(long pos) not supported", "0A000");
 	}
 
 	/**
-	 * Writes the given Java String to the CLOB  value that this
-	 * Clob object designates at the position pos.
+	 * Writes the given Java String to the CLOB value that this Clob object designates at the position pos.
+	 * The string will overwrite the existing characters in the Clob object starting at the position pos.
+	 * If the end of the Clob value is reached while writing the given string, then the length of
+	 * the Clob value will be increased to accomodate the extra characters.
 	 *
 	 * @param pos the position at which to start writing to the
 	 *        CLOB value that this Clob object represents
@@ -200,16 +257,21 @@ public class MonetClob implements Clob {
 	 *        this Clob designates
 	 * @return the number of characters written
 	 * @throws SQLException if there is an error accessing the
-	 *         CLOB value
+	 *         CLOB value or if pos is less than 1
 	 */
 	@Override
 	public int setString(long pos, String str) throws SQLException {
-		return setString(pos, str, 1, str.length());
+		if (str == null) {
+			throw new SQLException("Missing str object", "M1M05");
+		}
+		return setString(pos, str, 0, str.length());
 	}
 
 	/**
-	 * Writes len characters of str, starting at character offset,
-	 * to the CLOB value that this Clob represents.
+	 * Writes len characters of str, starting at character offset, to the CLOB value that this Clob represents.
+	 * The string will overwrite the existing characters in the Clob object starting at the position pos.
+	 * If the end of the Clob value is reached while writing the given string, then the length of
+	 * the Clob value will be increased to accomodate the extra characters.
 	 *
 	 * @param pos the position at which to start writing to this
 	 *        CLOB object
@@ -220,22 +282,32 @@ public class MonetClob implements Clob {
 	 * @param len the number of characters to be written
 	 * @return the number of characters written
 	 * @throws SQLException if there is an error accessing the
-	 *         CLOB value
+	 *         CLOB value or if pos is less than 1
 	 */
 	@Override
 	public int setString(long pos, String str, int offset, int len)
 		throws SQLException
 	{
 		checkBufIsNotNull();
+		if (str == null) {
+			throw new SQLException("Missing str object", "M1M05");
+		}
+		if (pos < 1 || pos > Integer.MAX_VALUE) {
+			throw new SQLException("Invalid pos value: " + pos, "M1M05");
+		}
+		if (offset < 0 || offset > str.length()) {
+			throw new SQLException("Invalid offset value: " + offset, "M1M05");
+		}
+		if (len < 1 || (offset + len) > str.length()) {
+			throw new SQLException("Invalid len value: " + len, "M1M05");
+		}
 
-		int buflen = buf.length();
-		int retlen = Math.min(buflen, (int)(pos - 1 + len));
-		if (retlen > 0) {
-			buf.replace((int)(pos - 1), (int)(pos + retlen), str.substring(offset - 1, (offset + len)));
-			return retlen;
-		} else {
-			return 0;
+		int ipos = (int) pos;
+		if ((ipos + len) > buf.capacity()) {
+			buf.ensureCapacity(ipos + len);
 		}
+		buf.replace(ipos - 1, ipos + len, str.substring(offset, (offset + len)));
+		return len;
 	}
 
 	/**
@@ -245,22 +317,25 @@ public class MonetClob implements Clob {
 	 * @param len the length, in bytes, to which the CLOB value
 	 *        should be truncated
 	 * @throws SQLException if there is an error accessing the
-	 *         CLOB value
+	 *         CLOB value or if len is less than 0
 	 */
 	@Override
 	public void truncate(long len) throws SQLException {
 		checkBufIsNotNull();
-		// this command is a no-op
+		if (len < 0 || len > buf.length()) {
+			throw new SQLException("Invalid len value: " + len, "M1M05");
+		}
+		buf.delete((int)len, buf.length());
 	}
 
+
 	/**
 	 * Returns the String behind this Clob.  This is a MonetClob
 	 * extension that does not violate nor is described in the Clob
 	 * interface.
 	 *
-	 * @return the String this Clob wraps.
+	 * @return the String this Clob wraps or "null" when this Clob is freed.
 	 */
-	@Override
 	public String toString() {
 		return (buf == null) ? "null" : buf.toString();
 	}