View Javadoc
1   /*
2    * Copyright (c) 2003, 2013, 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  import java.util.Arrays;
31  
32  /**
33   * A serialized mapping in the Java programming language of an SQL
34   * <code>CLOB</code> value.
35   * <P>
36   * The <code>SerialClob</code> class provides a constructor for creating
37   * an instance from a <code>Clob</code> object.  Note that the <code>Clob</code>
38   * object should have brought the SQL <code>CLOB</code> value's data over
39   * to the client before a <code>SerialClob</code> object
40   * is constructed from it.  The data of an SQL <code>CLOB</code> value can
41   * be materialized on the client as a stream of Unicode characters.
42   * <P>
43   * <code>SerialClob</code> methods make it possible to get a substring
44   * from a <code>SerialClob</code> object or to locate the start of
45   * a pattern of characters.
46   *
47   * <h3> Thread safety </h3>
48   *
49   * <p> A SerialClob is not safe for use by multiple concurrent threads.  If a
50   * SerialClob is to be used by more than one thread then access to the SerialClob
51   * should be controlled by appropriate synchronization.
52   * @author Jonathan Bruce
53   */
54  public class SerialClob implements Clob, Serializable, Cloneable {
55  
56      /**
57       * A serialized array of characters containing the data of the SQL
58       * <code>CLOB</code> value that this <code>SerialClob</code> object
59       * represents.
60       *
61       * @serial
62       */
63      private char buf[];
64  
65      /**
66       * Internal Clob representation if SerialClob is initialized with a
67       * Clob. Null if SerialClob is initialized with a char[].
68       */
69      private Clob clob;
70  
71      /**
72       * The length in characters of this <code>SerialClob</code> object's
73       * internal array of characters.
74       *
75       * @serial
76       */
77      private long len;
78  
79      /**
80       * The original length in characters of this <code>SerialClob</code>
81       * object's internal array of characters.
82       *
83       * @serial
84       */
85      private long origLen;
86  
87      /**
88       * Constructs a <code>SerialClob</code> object that is a serialized version of
89       * the given <code>char</code> array.
90       * <p>
91       * The new <code>SerialClob</code> object is initialized with the data from the
92       * <code>char</code> array, thus allowing disconnected <code>RowSet</code>
93       * objects to establish a serialized <code>Clob</code> object without touching
94       * the data source.
95       *
96       * @param ch the char array representing the <code>Clob</code> object to be
97       *         serialized
98       * @throws SerialException if an error occurs during serialization
99       * @throws SQLException if a SQL error occurs
100      */
101     public SerialClob(char ch[]) throws SerialException, SQLException {
102 
103         // %%% JMB. Agreed. Add code here to throw a SQLException if no
104         // support is available for locatorsUpdateCopy=false
105         // Serializing locators is not supported.
106 
107         len = ch.length;
108         buf = new char[(int)len];
109         for (int i = 0; i < len ; i++){
110            buf[i] = ch[i];
111         }
112         origLen = len;
113         clob = null;
114     }
115 
116     /**
117      * Constructs a <code>SerialClob</code> object that is a serialized
118      * version of the given <code>Clob</code> object.
119      * <P>
120      * The new <code>SerialClob</code> object is initialized with the
121      * data from the <code>Clob</code> object; therefore, the
122      * <code>Clob</code> object should have previously brought the
123      * SQL <code>CLOB</code> value's data over to the client from
124      * the database. Otherwise, the new <code>SerialClob</code> object
125      * object will contain no data.
126      * <p>
127      * Note: The <code>Clob</code> object supplied to this constructor must
128      * return non-null for both the <code>Clob.getCharacterStream()</code>
129      * and <code>Clob.getAsciiStream</code> methods. This <code>SerialClob</code>
130      * constructor cannot serialize a <code>Clob</code> object in this instance
131      * and will throw an <code>SQLException</code> object.
132      *
133      * @param  clob the <code>Clob</code> object from which this
134      *     <code>SerialClob</code> object is to be constructed; cannot be null
135      * @throws SerialException if an error occurs during serialization
136      * @throws SQLException if a SQL error occurs in capturing the CLOB;
137      *     if the <code>Clob</code> object is a null; or if either of the
138      *     <code>Clob.getCharacterStream()</code> and <code>Clob.getAsciiStream()</code>
139      *     methods on the <code>Clob</code> returns a null
140      * @see java.sql.Clob
141      */
142     public SerialClob(Clob clob) throws SerialException, SQLException {
143 
144         if (clob == null) {
145             throw new SQLException("Cannot instantiate a SerialClob " +
146                 "object with a null Clob object");
147         }
148         len = clob.length();
149         this.clob = clob;
150         buf = new char[(int)len];
151         int read = 0;
152         int offset = 0;
153 
154         try (Reader charStream = clob.getCharacterStream()) {
155             if (charStream == null) {
156                 throw new SQLException("Invalid Clob object. The call to getCharacterStream " +
157                     "returned null which cannot be serialized.");
158             }
159 
160             // Note: get an ASCII stream in order to null-check it,
161             // even though we don't do anything with it.
162             try (InputStream asciiStream = clob.getAsciiStream()) {
163                 if (asciiStream == null) {
164                     throw new SQLException("Invalid Clob object. The call to getAsciiStream " +
165                         "returned null which cannot be serialized.");
166                 }
167             }
168 
169             try (Reader reader = new BufferedReader(charStream)) {
170                 do {
171                     read = reader.read(buf, offset, (int)(len - offset));
172                     offset += read;
173                 } while (read > 0);
174             }
175         } catch (java.io.IOException ex) {
176             throw new SerialException("SerialClob: " + ex.getMessage());
177         }
178 
179         origLen = len;
180     }
181 
182     /**
183      * Retrieves the number of characters in this <code>SerialClob</code>
184      * object's array of characters.
185      *
186      * @return a <code>long</code> indicating the length in characters of this
187      *         <code>SerialClob</code> object's array of character
188      * @throws SerialException if an error occurs;
189      * if {@code free} had previously been called on this object
190      */
191     public long length() throws SerialException {
192         isValid();
193         return len;
194     }
195 
196     /**
197      * Returns this <code>SerialClob</code> object's data as a stream
198      * of Unicode characters. Unlike the related method, <code>getAsciiStream</code>,
199      * a stream is produced regardless of whether the <code>SerialClob</code> object
200      * was created with a <code>Clob</code> object or a <code>char</code> array.
201      *
202      * @return a <code>java.io.Reader</code> object containing this
203      *         <code>SerialClob</code> object's data
204      * @throws SerialException if an error occurs;
205      * if {@code free} had previously been called on this object
206      */
207     public java.io.Reader getCharacterStream() throws SerialException {
208         isValid();
209         return (java.io.Reader) new CharArrayReader(buf);
210     }
211 
212     /**
213      * Retrieves the <code>CLOB</code> value designated by this <code>SerialClob</code>
214      * object as an ascii stream. This method forwards the <code>getAsciiStream</code>
215      * call to the underlying <code>Clob</code> object in the event that this
216      * <code>SerialClob</code> object is instantiated with a <code>Clob</code>
217      * object. If this <code>SerialClob</code> object is instantiated with
218      * a <code>char</code> array, a <code>SerialException</code> object is thrown.
219      *
220      * @return a <code>java.io.InputStream</code> object containing
221      *     this <code>SerialClob</code> object's data
222      * @throws SerialException if this {@code SerialClob} object was not
223      * instantiated with a <code>Clob</code> object;
224      * if {@code free} had previously been called on this object
225      * @throws SQLException if there is an error accessing the
226      *     <code>CLOB</code> value represented by the <code>Clob</code> object
227      * that was used to create this <code>SerialClob</code> object
228      */
229     public java.io.InputStream getAsciiStream() throws SerialException, SQLException {
230         isValid();
231         if (this.clob != null) {
232             return this.clob.getAsciiStream();
233         } else {
234             throw new SerialException("Unsupported operation. SerialClob cannot " +
235                 "return a the CLOB value as an ascii stream, unless instantiated " +
236                 "with a fully implemented Clob object.");
237         }
238     }
239 
240     /**
241      * Returns a copy of the substring contained in this
242      * <code>SerialClob</code> object, starting at the given position
243      * and continuing for the specified number or characters.
244      *
245      * @param pos the position of the first character in the substring
246      *            to be copied; the first character of the
247      *            <code>SerialClob</code> object is at position
248      *            <code>1</code>; must not be less than <code>1</code>,
249      *            and the sum of the starting position and the length
250      *            of the substring must be less than the length of this
251      *            <code>SerialClob</code> object
252      * @param length the number of characters in the substring to be
253      *               returned; must not be greater than the length of
254      *               this <code>SerialClob</code> object, and the
255      *               sum of the starting position and the length
256      *               of the substring must be less than the length of this
257      *               <code>SerialClob</code> object
258      * @return a <code>String</code> object containing a substring of
259      *         this <code>SerialClob</code> object beginning at the
260      *         given position and containing the specified number of
261      *         consecutive characters
262      * @throws SerialException if either of the arguments is out of bounds;
263      * if {@code free} had previously been called on this object
264      */
265     public String getSubString(long pos, int length) throws SerialException {
266 
267         isValid();
268         if (pos < 1 || pos > this.length()) {
269             throw new SerialException("Invalid position in SerialClob object set");
270         }
271 
272         if ((pos-1) + length > this.length()) {
273             throw new SerialException("Invalid position and substring length");
274         }
275 
276         try {
277             return new String(buf, (int)pos - 1, length);
278 
279         } catch (StringIndexOutOfBoundsException e) {
280             throw new SerialException("StringIndexOutOfBoundsException: " +
281                 e.getMessage());
282         }
283 
284     }
285 
286     /**
287      * Returns the position in this <code>SerialClob</code> object
288      * where the given <code>String</code> object begins, starting
289      * the search at the specified position. This method returns
290      * <code>-1</code> if the pattern is not found.
291      *
292      * @param searchStr the <code>String</code> object for which to
293      *                  search
294      * @param start the position in this <code>SerialClob</code> object
295      *         at which to start the search; the first position is
296      *         <code>1</code>; must not be less than <code>1</code> nor
297      *         greater than the length of this <code>SerialClob</code> object
298      * @return the position at which the given <code>String</code> object
299      *         begins, starting the search at the specified position;
300      *         <code>-1</code> if the given <code>String</code> object is
301      *         not found or the starting position is out of bounds; position
302      *         numbering for the return value starts at <code>1</code>
303      * @throws SerialException  if the {@code free} method had been
304      * previously called on this object
305      * @throws SQLException if there is an error accessing the Clob value
306      *         from the database.
307      */
308     public long position(String searchStr, long start)
309         throws SerialException, SQLException {
310         isValid();
311         if (start < 1 || start > len) {
312             return -1;
313         }
314 
315         char pattern[] = searchStr.toCharArray();
316 
317         int pos = (int)start-1;
318         int i = 0;
319         long patlen = pattern.length;
320 
321         while (pos < len) {
322             if (pattern[i] == buf[pos]) {
323                 if (i + 1 == patlen) {
324                     return (pos + 1) - (patlen - 1);
325                 }
326                 i++; pos++; // increment pos, and i
327 
328             } else if (pattern[i] != buf[pos]) {
329                 pos++; // increment pos only
330             }
331         }
332         return -1; // not found
333     }
334 
335     /**
336      * Returns the position in this <code>SerialClob</code> object
337      * where the given <code>Clob</code> signature begins, starting
338      * the search at the specified position. This method returns
339      * <code>-1</code> if the pattern is not found.
340      *
341      * @param searchStr the <code>Clob</code> object for which to search
342      * @param start the position in this <code>SerialClob</code> object
343      *        at which to begin the search; the first position is
344      *         <code>1</code>; must not be less than <code>1</code> nor
345      *         greater than the length of this <code>SerialClob</code> object
346      * @return the position at which the given <code>Clob</code>
347      *         object begins in this <code>SerialClob</code> object,
348      *         at or after the specified starting position
349      * @throws SerialException if an error occurs locating the Clob signature;
350      * if the {@code free} method had been previously called on this object
351      * @throws SQLException if there is an error accessing the Clob value
352      *         from the database
353      */
354     public long position(Clob searchStr, long start)
355         throws SerialException, SQLException {
356         isValid();
357         return position(searchStr.getSubString(1,(int)searchStr.length()), start);
358     }
359 
360     /**
361      * Writes the given Java <code>String</code> to the <code>CLOB</code>
362      * value that this <code>SerialClob</code> object represents, at the position
363      * <code>pos</code>.
364      *
365      * @param pos the position at which to start writing to the <code>CLOB</code>
366      *         value that this <code>SerialClob</code> object represents; the first
367      *         position is <code>1</code>; must not be less than <code>1</code> nor
368      *         greater than the length of this <code>SerialClob</code> object
369      * @param str the string to be written to the <code>CLOB</code>
370      *        value that this <code>SerialClob</code> object represents
371      * @return the number of characters written
372      * @throws SerialException if there is an error accessing the
373      *     <code>CLOB</code> value; if an invalid position is set; if an
374      *     invalid offset value is set; if number of bytes to be written
375      *     is greater than the <code>SerialClob</code> length; or the combined
376      *     values of the length and offset is greater than the Clob buffer;
377      * if the {@code free} method had been previously called on this object
378      */
379     public int setString(long pos, String str) throws SerialException {
380         return (setString(pos, str, 0, str.length()));
381     }
382 
383     /**
384      * Writes <code>len</code> characters of <code>str</code>, starting
385      * at character <code>offset</code>, to the <code>CLOB</code> value
386      * that this <code>Clob</code> represents.
387      *
388      * @param pos the position at which to start writing to the <code>CLOB</code>
389      *         value that this <code>SerialClob</code> object represents; the first
390      *         position is <code>1</code>; must not be less than <code>1</code> nor
391      *         greater than the length of this <code>SerialClob</code> object
392      * @param str the string to be written to the <code>CLOB</code>
393      *        value that this <code>Clob</code> object represents
394      * @param offset the offset into <code>str</code> to start reading
395      *        the characters to be written
396      * @param length the number of characters to be written
397      * @return the number of characters written
398      * @throws SerialException if there is an error accessing the
399      *     <code>CLOB</code> value; if an invalid position is set; if an
400      *     invalid offset value is set; if number of bytes to be written
401      *     is greater than the <code>SerialClob</code> length; or the combined
402      *     values of the length and offset is greater than the Clob buffer;
403      * if the {@code free} method had been previously called on this object
404      */
405     public int setString(long pos, String str, int offset, int length)
406         throws SerialException {
407         isValid();
408         String temp = str.substring(offset);
409         char cPattern[] = temp.toCharArray();
410 
411         if (offset < 0 || offset > str.length()) {
412             throw new SerialException("Invalid offset in byte array set");
413         }
414 
415         if (pos < 1 || pos > this.length()) {
416             throw new SerialException("Invalid position in Clob object set");
417         }
418 
419         if ((long)(length) > origLen) {
420             throw new SerialException("Buffer is not sufficient to hold the value");
421         }
422 
423         if ((length + offset) > str.length()) {
424             // need check to ensure length + offset !> bytes.length
425             throw new SerialException("Invalid OffSet. Cannot have combined offset " +
426                 " and length that is greater that the Blob buffer");
427         }
428 
429         int i = 0;
430         pos--;  //values in the array are at position one less
431         while ( i < length || (offset + i +1) < (str.length() - offset ) ) {
432             this.buf[(int)pos + i ] = cPattern[offset + i ];
433             i++;
434         }
435         return i;
436     }
437 
438     /**
439      * Retrieves a stream to be used to write Ascii characters to the
440      * <code>CLOB</code> value that this <code>SerialClob</code> object represents,
441      * starting at position <code>pos</code>. This method forwards the
442      * <code>setAsciiStream()</code> call to the underlying <code>Clob</code> object in
443      * the event that this <code>SerialClob</code> object is instantiated with a
444      * <code>Clob</code> object. If this <code>SerialClob</code> object is instantiated
445      *  with a <code>char</code> array, a <code>SerialException</code> object is thrown.
446      *
447      * @param pos the position at which to start writing to the
448      *        <code>CLOB</code> object
449      * @return the stream to which ASCII encoded characters can be written
450      * @throws SerialException if SerialClob is not instantiated with a
451      *     Clob object;
452      * if the {@code free} method had been previously called on this object
453      * @throws SQLException if there is an error accessing the
454      *     <code>CLOB</code> value
455      * @see #getAsciiStream
456      */
457     public java.io.OutputStream setAsciiStream(long pos)
458         throws SerialException, SQLException {
459         isValid();
460          if (this.clob != null) {
461              return this.clob.setAsciiStream(pos);
462          } else {
463              throw new SerialException("Unsupported operation. SerialClob cannot " +
464                 "return a writable ascii stream\n unless instantiated with a Clob object " +
465                 "that has a setAsciiStream() implementation");
466          }
467     }
468 
469     /**
470      * Retrieves a stream to be used to write a stream of Unicode characters
471      * to the <code>CLOB</code> value that this <code>SerialClob</code> object
472      * represents, at position <code>pos</code>. This method forwards the
473      * <code>setCharacterStream()</code> call to the underlying <code>Clob</code>
474      * object in the event that this <code>SerialClob</code> object is instantiated with a
475      * <code>Clob</code> object. If this <code>SerialClob</code> object is instantiated with
476      * a <code>char</code> array, a <code>SerialException</code> is thrown.
477      *
478      * @param  pos the position at which to start writing to the
479      *        <code>CLOB</code> value
480      *
481      * @return a stream to which Unicode encoded characters can be written
482      * @throws SerialException if the SerialClob is not instantiated with
483      *     a Clob object;
484      * if the {@code free} method had been previously called on this object
485      * @throws SQLException if there is an error accessing the
486      *            <code>CLOB</code> value
487      * @see #getCharacterStream
488      */
489     public java.io.Writer setCharacterStream(long pos)
490         throws SerialException, SQLException {
491         isValid();
492         if (this.clob != null) {
493             return this.clob.setCharacterStream(pos);
494         } else {
495             throw new SerialException("Unsupported operation. SerialClob cannot " +
496                 "return a writable character stream\n unless instantiated with a Clob object " +
497                 "that has a setCharacterStream implementation");
498         }
499     }
500 
501     /**
502      * Truncates the <code>CLOB</code> value that this <code>SerialClob</code>
503      * object represents so that it has a length of <code>len</code>
504      * characters.
505      * <p>
506      * Truncating a <code>SerialClob</code> object to length 0 has the effect of
507      * clearing its contents.
508      *
509      * @param length the length, in bytes, to which the <code>CLOB</code>
510      *        value should be truncated
511      * @throws SerialException if there is an error accessing the
512      *        <code>CLOB</code> value;
513      * if the {@code free} method had been previously called on this object
514      */
515     public void truncate(long length) throws SerialException {
516         isValid();
517         if (length > len) {
518            throw new SerialException
519               ("Length more than what can be truncated");
520         } else {
521              len = length;
522              // re-size the buffer
523 
524              if (len == 0) {
525                 buf = new char[] {};
526              } else {
527                 buf = (this.getSubString(1, (int)len)).toCharArray();
528              }
529         }
530     }
531 
532 
533     /**
534      * Returns a {@code Reader} object that contains a partial
535      * {@code SerialClob} value, starting
536      * with the character specified by pos, which is length characters in length.
537      *
538      * @param pos the offset to the first character of the partial value to
539      * be retrieved.  The first character in the {@code SerialClob} is at position 1.
540      * @param length the length in characters of the partial value to be retrieved.
541      * @return {@code Reader} through which the partial {@code SerialClob}
542      * value can be read.
543      * @throws SQLException if pos is less than 1 or if pos is greater than the
544      * number of characters in the {@code SerialClob} or if pos + length
545      * is greater than the number of characters in the {@code SerialClob};
546      * @throws SerialException if the {@code free} method had been previously
547      * called on this object
548      * @since 1.6
549      */
550     public Reader getCharacterStream(long pos, long length) throws SQLException {
551         isValid();
552         if (pos < 1 || pos > len) {
553             throw new SerialException("Invalid position in Clob object set");
554         }
555 
556         if ((pos-1) + length > len) {
557             throw new SerialException("Invalid position and substring length");
558         }
559         if (length <= 0) {
560             throw new SerialException("Invalid length specified");
561         }
562         return new CharArrayReader(buf, (int)pos, (int)length);
563     }
564 
565     /**
566      * This method frees the {@code SeriableClob} object and releases the
567      * resources that it holds.
568      * The object is invalid once the {@code free} method is called.
569      * <p>
570      * If {@code free} is called multiple times, the subsequent
571      * calls to {@code free} are treated as a no-op.
572      * </P>
573      * @throws SQLException if an error occurs releasing
574      * the Clob's resources
575      * @since 1.6
576      */
577     public void free() throws SQLException {
578         if (buf != null) {
579             buf = null;
580             if (clob != null) {
581                 clob.free();
582             }
583             clob = null;
584         }
585     }
586 
587     /**
588      * Compares this SerialClob to the specified object.  The result is {@code
589      * true} if and only if the argument is not {@code null} and is a {@code
590      * SerialClob} object that represents the same sequence of characters as this
591      * object.
592      *
593      * @param  obj The object to compare this {@code SerialClob} against
594      *
595      * @return  {@code true} if the given object represents a {@code SerialClob}
596      *          equivalent to this SerialClob, {@code false} otherwise
597      *
598      */
599     public boolean equals(Object obj) {
600         if (this == obj) {
601             return true;
602         }
603         if (obj instanceof SerialClob) {
604             SerialClob sc = (SerialClob)obj;
605             if (this.len == sc.len) {
606                 return Arrays.equals(buf, sc.buf);
607             }
608         }
609         return false;
610     }
611 
612     /**
613      * Returns a hash code for this {@code SerialClob}.
614      * @return  a hash code value for this object.
615      */
616     public int hashCode() {
617        return ((31 + Arrays.hashCode(buf)) * 31 + (int)len) * 31 + (int)origLen;
618     }
619 
620     /**
621      * Returns a clone of this {@code SerialClob}. The copy will contain a
622      * reference to a clone of the internal character array, not a reference
623      * to the original internal character array of this {@code SerialClob} object.
624      * The underlying {@code Clob} object will be set to null.
625      *
626      * @return  a clone of this SerialClob
627      */
628     public Object clone() {
629         try {
630             SerialClob sc = (SerialClob) super.clone();
631             sc.buf = (buf != null) ? Arrays.copyOf(buf, (int)len) : null;
632             sc.clob = null;
633             return sc;
634         } catch (CloneNotSupportedException ex) {
635             // this shouldn't happen, since we are Cloneable
636             throw new InternalError();
637         }
638     }
639 
640     /**
641      * readObject is called to restore the state of the SerialClob from
642      * a stream.
643      */
644     private void readObject(ObjectInputStream s)
645             throws IOException, ClassNotFoundException {
646 
647         ObjectInputStream.GetField fields = s.readFields();
648        char[] tmp = (char[])fields.get("buf", null);
649        if (tmp == null)
650            throw new InvalidObjectException("buf is null and should not be!");
651        buf = tmp.clone();
652        len = fields.get("len", 0L);
653        if (buf.length != len)
654            throw new InvalidObjectException("buf is not the expected size");
655        origLen = fields.get("origLen", 0L);
656        clob = (Clob) fields.get("clob", null);
657     }
658 
659     /**
660      * writeObject is called to save the state of the SerialClob
661      * to a stream.
662      */
663     private void writeObject(ObjectOutputStream s)
664             throws IOException, ClassNotFoundException {
665 
666         ObjectOutputStream.PutField fields = s.putFields();
667         fields.put("buf", buf);
668         fields.put("len", len);
669         fields.put("origLen", origLen);
670         // Note: this check to see if it is an instance of Serializable
671         // is for backwards compatibiity
672         fields.put("clob", clob instanceof Serializable ? clob : null);
673         s.writeFields();
674     }
675 
676     /**
677      * Check to see if this object had previously had its {@code free} method
678      * called
679      *
680      * @throws SerialException
681      */
682     private void isValid() throws SerialException {
683         if (buf == null) {
684             throw new SerialException("Error: You cannot call a method on a "
685                     + "SerialClob instance once free() has been called.");
686         }
687     }
688 
689     /**
690      * The identifier that assists in the serialization of this {@code SerialClob}
691      * object.
692      */
693     static final long serialVersionUID = -1662519690087375313L;
694 }