1 /* 2 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.sql.rowset.serial; 27 28 import java.sql.*; 29 import java.io.*; 30 31 /** 32 * A serialized mapping in the Java programming language of an SQL 33 * <code>CLOB</code> value. 34 * <P> 35 * The <code>SerialClob</code> class provides a constructor for creating 36 * an instance from a <code>Clob</code> object. Note that the <code>Clob</code> 37 * object should have brought the SQL <code>CLOB</code> value's data over 38 * to the client before a <code>SerialClob</code> object 39 * is constructed from it. The data of an SQL <code>CLOB</code> value can 40 * be materialized on the client as a stream of Unicode characters. 41 * <P> 42 * <code>SerialClob</code> methods make it possible to get a substring 43 * from a <code>SerialClob</code> object or to locate the start of 44 * a pattern of characters. 45 * 46 * @author Jonathan Bruce 47 */ 48 public class SerialClob implements Clob, Serializable, Cloneable { 49 50 /** 51 * A serialized array of characters containing the data of the SQL 52 * <code>CLOB</code> value that this <code>SerialClob</code> object 53 * represents. 54 * 55 * @serial 56 */ 57 private char buf[]; 58 59 /** 60 * Internal Clob representation if SerialClob is initialized with a 61 * Clob. Null if SerialClob is initialized with a char[]. 62 */ 63 private Clob clob; 64 65 /** 66 * The length in characters of this <code>SerialClob</code> object's 67 * internal array of characters. 68 * 69 * @serial 70 */ 71 private long len; 72 73 /** 74 * The original length in characters of this <code>SerialClob</code> 75 * object's internal array of characters. 76 * 77 * @serial 78 */ 79 private long origLen; 80 81 private boolean isFree =false; 82 83 /** 84 * Constructs a <code>SerialClob</code> object that is a serialized version of 85 * the given <code>char</code> array. 86 * <p> 87 * The new <code>SerialClob</code> object is initialized with the data from the 88 * <code>char</code> array, thus allowing disconnected <code>RowSet</code> 89 * objects to establish a serialized <code>Clob</code> object without touching 90 * the data source. 91 * 92 * @param ch the char array representing the <code>Clob</code> object to be 93 * serialized 94 * @throws SerialException if an error occurs during serialization 95 * @throws SQLException if a SQL error occurs 96 */ 97 public SerialClob(char ch[]) throws SerialException, SQLException { 98 99 // %%% JMB. Agreed. Add code here to throw a SQLException if no 100 // support is available for locatorsUpdateCopy=false 101 // Serializing locators is not supported. 102 103 len = ch.length; 104 buf = new char[(int)len]; 105 for (int i = 0; i < len ; i++){ 106 buf[i] = ch[i]; 107 } 108 origLen = len; 109 clob = null; 110 } 111 112 /** 113 * Constructs a <code>SerialClob</code> object that is a serialized 114 * version of the given <code>Clob</code> object. 115 * <P> 116 * The new <code>SerialClob</code> object is initialized with the 117 * data from the <code>Clob</code> object; therefore, the 118 * <code>Clob</code> object should have previously brought the 119 * SQL <code>CLOB</code> value's data over to the client from 120 * the database. Otherwise, the new <code>SerialClob</code> object 121 * object will contain no data. 122 * <p> 123 * Note: The <code>Clob</code> object supplied to this constructor must 124 * return non-null for both the <code>Clob.getCharacterStream()</code> 125 * and <code>Clob.getAsciiStream</code> methods. This <code>SerialClob</code> 126 * constructor cannot serialize a <code>Clob</code> object in this instance 127 * and will throw an <code>SQLException</code> object. 128 * 129 * @param clob the <code>Clob</code> object from which this 130 * <code>SerialClob</code> object is to be constructed; cannot be null 131 * @throws SerialException if an error occurs during serialization 132 * @throws SQLException if a SQL error occurs in capturing the CLOB; 133 * if the <code>Clob</code> object is a null; or if either of the 134 * <code>Clob.getCharacterStream()</code> and <code>Clob.getAsciiStream()</code> 135 * methods on the <code>Clob</code> returns a null 136 * @see java.sql.Clob 137 */ 138 public SerialClob(Clob clob) throws SerialException, SQLException { 139 140 if (clob == null) { 141 throw new SQLException("Cannot instantiate a SerialClob " + 142 "object with a null Clob object"); 143 } 144 len = clob.length(); 145 this.clob = clob; 146 buf = new char[(int)len]; 147 int read = 0; 148 int offset = 0; 149 150 try (Reader charStream = clob.getCharacterStream()) { 151 if (charStream == null) { 152 throw new SQLException("Invalid Clob object. The call to getCharacterStream " + 153 "returned null which cannot be serialized."); 154 } 155 156 // Note: get an ASCII stream in order to null-check it, 157 // even though we don't do anything with it. 158 try (InputStream asciiStream = clob.getAsciiStream()) { 159 if (asciiStream == null) { 160 throw new SQLException("Invalid Clob object. The call to getAsciiStream " + 161 "returned null which cannot be serialized."); 162 } 163 } 164 165 try (Reader reader = new BufferedReader(charStream)) { 166 do { 167 read = reader.read(buf, offset, (int)(len - offset)); 168 offset += read; 169 } while (read > 0); 170 } 171 } catch (java.io.IOException ex) { 172 throw new SerialException("SerialClob: " + ex.getMessage()); 173 } 174 175 origLen = len; 176 } 177 178 /** 179 * Retrieves the number of characters in this <code>SerialClob</code> 180 * object's array of characters. 181 * 182 * @return a <code>long</code> indicating the length in characters of this 183 * <code>SerialClob</code> object's array of character 184 * @throws SerialException if an error occurs or 185 * called after free() has been called. 186 */ 187 public long length() throws SerialException { 188 isFreed(); 189 return len; 190 } 191 192 /** 193 * Returns this <code>SerialClob</code> object's data as a stream 194 * of Unicode characters. Unlike the related method, <code>getAsciiStream</code>, 195 * a stream is produced regardless of whether the <code>SerialClob</code> object 196 * was created with a <code>Clob</code> object or a <code>char</code> array. 197 * 198 * @return a <code>java.io.Reader</code> object containing this 199 * <code>SerialClob</code> object's data 200 * @throws SerialException if an error occurs or 201 * called after free() has been called. 202 */ 203 public java.io.Reader getCharacterStream() throws SerialException { 204 isFreed(); 205 return (java.io.Reader) new CharArrayReader(buf); 206 } 207 208 /** 209 * Retrieves the <code>CLOB</code> value designated by this <code>SerialClob</code> 210 * object as an ascii stream. This method forwards the <code>getAsciiStream</code> 211 * call to the underlying <code>Clob</code> object in the event that this 212 * <code>SerialClob</code> object is instantiated with a <code>Clob</code> 213 * object. If this <code>SerialClob</code> object is instantiated with 214 * a <code>char</code> array, a <code>SerialException</code> object is thrown. 215 * 216 * @return a <code>java.io.InputStream</code> object containing 217 * this <code>SerialClob</code> object's data 218 * @throws SerialException if this <code>SerialClob</code> object was not instantiated 219 * with a <code>Clob</code> object or called after free() has been 220 * called. 221 * @throws SQLException 222 * if there is an error accessing the 223 * <code>CLOB</code> value represented by the <code>Clob</code> object that was 224 * used to create this <code>SerialClob</code> object 225 */ 226 public java.io.InputStream getAsciiStream() throws SerialException, SQLException { 227 isFreed(); 228 if (this.clob != null) { 229 return this.clob.getAsciiStream(); 230 } else { 231 throw new SerialException("Unsupported operation. SerialClob cannot " + 232 "return a the CLOB value as an ascii stream, unless instantiated " + 233 "with a fully implemented Clob object."); 234 } 235 } 236 237 /** 238 * Returns a copy of the substring contained in this 239 * <code>SerialClob</code> object, starting at the given position 240 * and continuing for the specified number or characters. 241 * 242 * @param pos the position of the first character in the substring 243 * to be copied; the first character of the 244 * <code>SerialClob</code> object is at position 245 * <code>1</code>; must not be less than <code>1</code>, 246 * and the sum of the starting position and the length 247 * of the substring must be less than the length of this 248 * <code>SerialClob</code> object 249 * @param length the number of characters in the substring to be 250 * returned; must not be greater than the length of 251 * this <code>SerialClob</code> object, and the 252 * sum of the starting position and the length 253 * of the substring must be less than the length of this 254 * <code>SerialClob</code> object 255 * @return a <code>String</code> object containing a substring of 256 * this <code>SerialClob</code> object beginning at the 257 * given position and containing the specified number of 258 * consecutive characters 259 * @throws SerialException if either of the arguments is out of bounds or called 260 * after free() has been called. 261 */ 262 public String getSubString(long pos, int length) throws SerialException { 263 isFreed(); 264 265 if (pos < 1 || pos > this.length()) { 266 throw new SerialException("Invalid position in BLOB object set"); 267 } 268 269 if ((pos-1) + length > this.length()) { 270 throw new SerialException("Invalid position and substring length"); 271 } 272 273 try { 274 return new String(buf, (int)pos - 1, length); 275 276 } catch (StringIndexOutOfBoundsException e) { 277 throw new SerialException("StringIndexOutOfBoundsException: " + 278 e.getMessage()); 279 } 280 281 } 282 283 /** 284 * Returns the position in this <code>SerialClob</code> object 285 * where the given <code>String</code> object begins, starting 286 * the search at the specified position. This method returns 287 * <code>-1</code> if the pattern is not found. 288 * 289 * @param searchStr the <code>String</code> object for which to 290 * search 291 * @param start the position in this <code>SerialClob</code> object 292 * at which to start the search; the first position is 293 * <code>1</code>; must not be less than <code>1</code> nor 294 * greater than the length of this <code>SerialClob</code> object 295 * @return the position at which the given <code>String</code> object 296 * begins, starting the search at the specified position; 297 * <code>-1</code> if the given <code>String</code> object is 298 * not found or the starting position is out of bounds; position 299 * numbering for the return value starts at <code>1</code> 300 * @throws SerialException if an error occurs locating the String signature 301 * or called after free() has been called. 302 * @throws SQLException if there is an error accessing the Blob value 303 * from the database. 304 */ 305 public long position(String searchStr, long start) 306 throws SerialException, SQLException { 307 isFreed(); 308 309 if (start < 1 || start > len) { 310 return -1; 311 } 312 313 char pattern[] = searchStr.toCharArray(); 314 315 int pos = (int)start-1; 316 int i = 0; 317 long patlen = pattern.length; 318 319 while (pos < len) { 320 if (pattern[i] == buf[pos]) { 321 if (i + 1 == patlen) { 322 return (pos + 1) - (patlen - 1); 323 } 324 i++; pos++; // increment pos, and i 325 326 } else if (pattern[i] != buf[pos]) { 327 pos++; // increment pos only 328 } 329 } 330 return -1; // not found 331 } 332 333 /** 334 * Returns the position in this <code>SerialClob</code> object 335 * where the given <code>Clob</code> signature begins, starting 336 * the search at the specified position. This method returns 337 * <code>-1</code> if the pattern is not found. 338 * 339 * @param searchStr the <code>Clob</code> object for which to search 340 * @param start the position in this <code>SerialClob</code> object 341 * at which to begin the search; the first position is 342 * <code>1</code>; must not be less than <code>1</code> nor 343 * greater than the length of this <code>SerialClob</code> object 344 * @return the position at which the given <code>Clob</code> 345 * object begins in this <code>SerialClob</code> object, 346 * at or after the specified starting position 347 * @throws SerialException if an error occurs locating the Clob signature 348 * or called after free() has been called. 349 * @throws SQLException if there is an error accessing the Blob value 350 * from the database 351 */ 352 public long position(Clob searchStr, long start) 353 throws SerialException, SQLException { 354 isFreed(); 355 356 return position(searchStr.getSubString(1,(int)searchStr.length()), start); 357 } 358 359 /** 360 * Writes the given Java <code>String</code> to the <code>CLOB</code> 361 * value that this <code>SerialClob</code> object represents, at the position 362 * <code>pos</code>. 363 * 364 * @param pos the position at which to start writing to the <code>CLOB</code> 365 * value that this <code>SerialClob</code> object represents; the first 366 * position is <code>1</code>; must not be less than <code>1</code> nor 367 * greater than the length of this <code>SerialClob</code> object 368 * @param str the string to be written to the <code>CLOB</code> 369 * value that this <code>SerialClob</code> object represents 370 * @return the number of characters written 371 * @throws SerialException if there is an error accessing the 372 * <code>CLOB</code> value; if an invalid position is set; if an 373 * invalid offset value is set; if number of bytes to be written 374 * is greater than the <code>SerialClob</code> length; or the combined 375 * values of the length and offset is greater than the Clob buffer or 376 * called after free() has been called. 377 */ 378 public int setString(long pos, String str) throws SerialException { 379 isFreed(); 380 381 return (setString(pos, str, 0, str.length())); 382 } 383 384 /** 385 * Writes <code>len</code> characters of <code>str</code>, starting 386 * at character <code>offset</code>, to the <code>CLOB</code> value 387 * that this <code>Clob</code> represents. 388 * 389 * @param pos the position at which to start writing to the <code>CLOB</code> 390 * value that this <code>SerialClob</code> object represents; the first 391 * position is <code>1</code>; must not be less than <code>1</code> nor 392 * greater than the length of this <code>SerialClob</code> object 393 * @param str the string to be written to the <code>CLOB</code> 394 * value that this <code>Clob</code> object represents 395 * @param offset the offset into <code>str</code> to start reading 396 * the characters to be written 397 * @param length the number of characters to be written 398 * @return the number of characters written 399 * @throws SerialException if there is an error accessing the 400 * <code>CLOB</code> value; if an invalid position is set; if an 401 * invalid offset value is set; if number of bytes to be written 402 * is greater than the <code>SerialClob</code> length; or the combined 403 * values of the length and offset is greater than the Clob buffer or 404 * called after free() has been called. 405 */ 406 public int setString(long pos, String str, int offset, int length) 407 throws SerialException { 408 isFreed(); 409 410 String temp = str.substring(offset); 411 char cPattern[] = temp.toCharArray(); 412 413 if (offset < 0 || offset > str.length()) { 414 throw new SerialException("Invalid offset in byte array set"); 415 } 416 417 if (pos < 1 || pos > this.length()) { 418 throw new SerialException("Invalid position in BLOB object set"); 419 } 420 421 if ((long)(length) > origLen) { 422 throw new SerialException("Buffer is not sufficient to hold the value"); 423 } 424 425 if ((length + offset) > str.length()) { 426 // need check to ensure length + offset !> bytes.length 427 throw new SerialException("Invalid OffSet. Cannot have combined offset " + 428 " and length that is greater that the Blob buffer"); 429 } 430 431 int i = 0; 432 pos--; //values in the array are at position one less 433 while ( i < length || (offset + i +1) < (str.length() - offset ) ) { 434 this.buf[(int)pos + i ] = cPattern[offset + i ]; 435 i++; 436 } 437 return i; 438 } 439 440 /** 441 * Retrieves a stream to be used to write Ascii characters to the 442 * <code>CLOB</code> value that this <code>SerialClob</code> object represents, 443 * starting at position <code>pos</code>. This method forwards the 444 * <code>setAsciiStream()</code> call to the underlying <code>Clob</code> object in 445 * the event that this <code>SerialClob</code> object is instantiated with a 446 * <code>Clob</code> object. If this <code>SerialClob</code> object is instantiated 447 * with a <code>char</code> array, a <code>SerialException</code> object is thrown. 448 * 449 * @param pos the position at which to start writing to the 450 * <code>CLOB</code> object 451 * @return the stream to which ASCII encoded characters can be written 452 * @throws SerialException if SerialClob is not instantiated with a 453 * Clob object that supports <code>setAsciiStream</code> or 454 * called after free() has been called. 455 * @throws SQLException if there is an error accessing the 456 * <code>CLOB</code> value 457 * @see #getAsciiStream 458 */ 459 public java.io.OutputStream setAsciiStream(long pos) 460 throws SerialException, SQLException { 461 isFreed(); 462 463 if (this.clob != null) { 464 return this.clob.setAsciiStream(pos); 465 } else { 466 throw new SerialException("Unsupported operation. SerialClob cannot " + 467 "return a writable ascii stream\n unless instantiated with a Clob object " + 468 "that has a setAsciiStream() implementation"); 469 } 470 } 471 472 /** 473 * Retrieves a stream to be used to write a stream of Unicode characters 474 * to the <code>CLOB</code> value that this <code>SerialClob</code> object 475 * represents, at position <code>pos</code>. This method forwards the 476 * <code>setCharacterStream()</code> call to the underlying <code>Clob</code> 477 * object in the event that this <code>SerialClob</code> object is instantiated with a 478 * <code>Clob</code> object. If this <code>SerialClob</code> object is instantiated with 479 * a <code>char</code> array, a <code>SerialException</code> is thrown. 480 * 481 * @param pos the position at which to start writing to the 482 * <code>CLOB</code> value 483 * 484 * @return a stream to which Unicode encoded characters can be written 485 * @throws SerialException if the SerialClob is not instantiated with 486 * a Clob object that supports <code>setCharacterStream</code> or 487 * called after free() has been called. 488 * @throws SQLException if there is an error accessing the 489 * <code>CLOB</code> value 490 * @see #getCharacterStream 491 */ 492 public java.io.Writer setCharacterStream(long pos) 493 throws SerialException, SQLException { 494 isFreed(); 495 496 if (this.clob != null) { 497 return this.clob.setCharacterStream(pos); 498 } else { 499 throw new SerialException("Unsupported operation. SerialClob cannot " + 500 "return a writable character stream\n unless instantiated with a Clob object " + 501 "that has a setCharacterStream implementation"); 502 } 503 } 504 505 /** 506 * Truncates the <code>CLOB</code> value that this <code>SerialClob</code> 507 * object represents so that it has a length of <code>len</code> 508 * characters. 509 * <p> 510 * Truncating a <code>SerialClob</code> object to length 0 has the effect of 511 * clearing its contents. 512 * 513 * @param length the length, in bytes, to which the <code>CLOB</code> 514 * value should be truncated 515 * @throws SQLException if there is an error accessing the 516 * <code>CLOB</code> value 517 * @throws SerialException if called after free() has been called. 518 */ 519 public void truncate(long length) throws SerialException { 520 isFreed(); 521 522 if (length > len) { 523 throw new SerialException("Length more than what can be truncated"); 524 } else { 525 len = length; 526 // re-size the buffer 527 528 if (len == 0) { 529 buf = new char[] {}; 530 } else { 531 buf = (this.getSubString(1, (int) len)).toCharArray(); 532 } 533 534 } 535 } 536 537 /** 538 * Returns a <code>Reader</code> object that contains a partial 539 * <code>Clob</code> value, starting with the character specified by 540 * pos, which is length characters in length. 541 * 542 * @param pos 543 * the offset to the first character of the partial value 544 * to be retrieved. The first character in the 545 * <code>Clob</code> is at position 1. 546 * @param length 547 * the length in characters of the partial value to be 548 * retrieved. 549 * 550 * @return Reader through which the partial <code>Clob</code> value 551 * can be read. 552 * @throws SQLException 553 * if pos is less than 1 or if pos is greater than the 554 * number of characters in the <code>Clob</code> or if 555 * pos + length is greater than the number of characters 556 * in the <code>Clob</code> 557 * @throws SQLFeatureNotSupportedException 558 * if the JDBC driver does not support this method 559 * @throws SerialException if called after free() has been called. 560 */ 561 public Reader getCharacterStream(long pos, long length) throws SQLException { 562 isFreed(); 563 564 if (pos < 1 || pos > len) { 565 throw new SerialException("Invalid pos in getCharacterStream"); 566 } 567 if ((pos - 1) + length > len) { 568 throw new SerialException("pos + length greater than total number of bytes"); 569 } 570 return (java.io.Reader) new CharArrayReader(buf, (int) pos -1, (int)length); 571 } 572 573 /** 574 * This method frees the <code>Clob</code> object and releases the 575 * resources the resources that it holds. The object is invalid once 576 * the <code>free</code> method is called. After <code>free</code> 577 * has been called, any attempt to invoke a method other than 578 * <code>free</code> will result in a <code>SQLException</code> being 579 * thrown. If <code>free</code> is called multiple times, the 580 * subsequent calls to <code>free<c/ode> are treated as a no-op. 581 * 582 * @throws SQLException if an error occurs releasing the <code>Clob</code>'s 583 * resources 584 * @throws SQLFeatureNotSupportedException if the JDBC driver does 585 * not support this method 586 */ 587 public void free() throws SQLException { 588 if (isFree == false) { 589 len = -1; 590 origLen = -1; 591 buf = null; 592 593 if (clob != null) { 594 clob.free(); 595 clob = null; 596 } 597 598 isFree = true; 599 } 600 } 601 602 private void isFreed() throws SerialException { 603 if (isFree == true) { 604 throw new SerialException( 605 "Unsupported operation. SerialClob cannot " 606 + "execute this operation when it has already been freed by free()"); 607 } 608 } 609 610 /** 611 * The identifier that assists in the serialization of this <code>SerialClob</code> 612 * object. 613 */ 614 static final long serialVersionUID = -1662519690087375313L; 615 }