View Javadoc

1   package com.workingdogs.village;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayOutputStream;
23  import java.io.PrintWriter;
24  
25  import java.math.BigDecimal;
26  
27  import java.sql.Connection;
28  import java.sql.PreparedStatement;
29  import java.sql.ResultSet;
30  import java.sql.SQLException;
31  
32  /***
33   * A Record represents a row in the database. It contains a collection of <a href="Value.html">Values</A> which are the individual
34   * contents of each column in the row.
35   *
36   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
37   * @version $Revision: 568 $
38   */
39  public class Record
40  {
41      /*** an array of Value objects, this is 1 based */
42      private Value [] values;
43  
44      /*** a 1 To 1 relationship between Values and whether they are clean or not */
45      private boolean [] isClean;
46  
47      /*** the parent DataSet for this Record */
48      private DataSet parentDataSet;
49  
50      /*** number of columns in this Record */
51      private int numberOfColumns;
52  
53      /*** this is the state of this record */
54      private int saveType = 0;
55  
56      /*** a saved copy of the schema for this Record */
57      private Schema schema;
58  
59      /***
60       * This isn't used and doesn't do anything.
61       */
62      public Record()
63      {
64          // don't do anything
65      }
66  
67      /***
68       * Creates a new Record and sets the parent dataset to the passed in value. This method also creates the Value objects which
69       * are associated with this Record.
70       *
71       * @param ds TODO: DOCUMENT ME!
72       *
73       * @throws DataSetException TODO: DOCUMENT ME!
74       * @throws SQLException TODO: DOCUMENT ME!
75       */
76      public Record(DataSet ds)
77              throws DataSetException, SQLException
78      {
79          setParentDataSet(ds);
80          initializeRecord();
81          createValues(dataset().resultSet());
82      }
83  
84      /***
85       * This is a special case method for Record. This case is really only used when DataSet.addRecord() is called because we may
86       * not have an existing ResultSet so there will not be any values in the Value objects that are created. Passing null to
87       * createValues forces the Value object to be created, but no processing to be done within the Value object constructor.
88       *
89       * <P>
90       * This method is a package method only because it is really not useful outside of the package.
91       * </p>
92       *
93       * @param ds the dataset
94       * @param addRecord whether or not this method is being called from DataSet.addRecord()
95       *
96       * @throws DataSetException TODO: DOCUMENT ME!
97       * @throws SQLException TODO: DOCUMENT ME!
98       */
99      Record(DataSet ds, boolean addRecord)
100             throws DataSetException, SQLException
101     {
102         setParentDataSet(ds);
103         initializeRecord();
104         createValues(null);
105     }
106 
107     /***
108      * Performs initialization for this Record.
109      *
110      * @throws DataSetException TODO: DOCUMENT ME!
111      */
112     private void initializeRecord()
113             throws DataSetException
114     {
115         this.schema = dataset().schema();
116         this.numberOfColumns = schema.numberOfColumns();
117         this.values = new Value[size() + 1];
118         this.isClean = new boolean[size() + 1];
119         setSaveType(Enums.UNKNOWN);
120 
121         for (int i = 1; i <= size(); i++)
122         {
123             markValueClean(i);
124             this.values[i] = null;
125         }
126     }
127 
128     /***
129      * Creates the value objects for this Record. It is 1 based
130      *
131      * @param rs TODO: DOCUMENT ME!
132      *
133      * @exception DataSetException
134      * @exception SQLException
135      */
136     private void createValues(ResultSet rs)
137             throws DataSetException, SQLException
138     {
139         for (int i = 1; i <= size(); i++)
140         {
141             Value val = new Value(rs, i, schema().column(i).typeEnum());
142             this.values[i] = val;
143         }
144     }
145 
146     /***
147      * Saves the data in this Record to the database. Uses the parent dataset's connection.
148      *
149      * @return 1 if the save completed. 0 otherwise.
150      *
151      * @throws DataSetException TODO: DOCUMENT ME!
152      * @throws SQLException TODO: DOCUMENT ME!
153      */
154     public int save()
155             throws DataSetException, SQLException
156     {
157         return save(dataset().connection());
158     }
159 
160     /***
161      * Saves the data in this Record to the database. Uses the connection passed into it.
162      *
163      * @param connection TODO: DOCUMENT ME!
164      *
165      * @return 1 if the save completed. 0 otherwise.
166      *
167      * @throws DataSetException TODO: DOCUMENT ME!
168      * @throws SQLException TODO: DOCUMENT ME!
169      */
170     public int save(Connection connection)
171             throws DataSetException, SQLException
172     {
173         int returnValue = 0;
174 
175         if (dataset() instanceof QueryDataSet)
176         {
177             throw new DataSetException("You cannot save a QueryDataSet. Please use a TableDataSet instead.");
178         }
179 
180         if (!needsToBeSaved())
181         {
182             return returnValue;
183         }
184 
185         if (toBeSavedWithInsert())
186         {
187             returnValue = saveWithInsert(connection);
188         }
189         else if (toBeSavedWithUpdate())
190         {
191             returnValue = saveWithUpdate(connection);
192         }
193         else if (toBeSavedWithDelete())
194         {
195             returnValue = saveWithDelete(connection);
196         }
197 
198         return returnValue;
199     }
200 
201     /***
202      * Saves the data in this Record to the database with an DELETE statement
203      *
204      * @param connection TODO: DOCUMENT ME!
205      *
206      * @return SQL DELETE statement
207      *
208      * @throws DataSetException TODO: DOCUMENT ME!
209      * @throws SQLException TODO: DOCUMENT ME!
210      */
211     private int saveWithDelete(Connection connection)
212             throws DataSetException, SQLException
213     {
214         PreparedStatement stmt = null;
215 
216         try
217         {
218             stmt = connection.prepareStatement(getSaveString());
219 
220             int ps = 1;
221 
222             for (int i = 1; i <= dataset().keydef().size(); i++)
223             {
224                 Value val = getValue(dataset().keydef().getAttrib(i));
225 
226                 val.setPreparedStatementValue(stmt, ps++);
227             }
228 
229             int ret = stmt.executeUpdate();
230 
231             // note that the actual deletion of the Record objects
232             // from the TDS is now in the save() method of the TDS
233             // instead of here. This fixes a bug where multiple
234             // records would not be deleted properly because they
235             // were being removed from here and the Records Vector
236             // was getting out of sync with reality. So, just
237             // mark them as needing to be removed here.
238             setSaveType(Enums.ZOMBIE);
239 
240             if (ret > 1)
241             {
242                 throw new SQLException("There were " + ret + " rows deleted with this records key value.");
243             }
244 
245             return ret;
246         }
247         catch (SQLException e1)
248         {
249             throw e1;
250         }
251         finally
252         {
253             try
254             {
255                 if (stmt != null)
256                 {
257                     stmt.close();
258                 }
259             }
260             catch (SQLException e2)
261             {
262                 throw e2;
263             }
264         }
265     }
266 
267     /***
268      * Saves the data in this Record to the database with an UPDATE statement
269      *
270      * @param connection TODO: DOCUMENT ME!
271      *
272      * @return SQL UPDATE statement
273      *
274      * @throws DataSetException TODO: DOCUMENT ME!
275      * @throws SQLException TODO: DOCUMENT ME!
276      */
277     private int saveWithUpdate(Connection connection)
278             throws DataSetException, SQLException
279     {
280         PreparedStatement stmt = null;
281 
282         try
283         {
284             stmt = connection.prepareStatement(getSaveString());
285 
286             int ps = 1;
287 
288             for (int i = 1; i <= size(); i++)
289             {
290                 Value val = getValue(i);
291 
292                 if (!valueIsClean(i) && !schema().column(i).readOnly())
293                 {
294                     val.setPreparedStatementValue(stmt, ps++);
295                 }
296             }
297 
298             for (int i = 1; i <= dataset().keydef().size(); i++)
299             {
300                 Value val = getValue(dataset().keydef().getAttrib(i));
301 
302                 val.setPreparedStatementValue(stmt, ps++);
303             }
304 
305             int ret = stmt.executeUpdate();
306 
307             if (((TableDataSet) dataset()).refreshOnSave())
308             {
309                 refresh(dataset().connection());
310             }
311             else
312             {
313                 // Marks all of the values clean since they have now been saved
314                 markRecordClean();
315             }
316 
317             setSaveType(Enums.AFTERUPDATE);
318 
319             if (ret > 1)
320             {
321                 throw new SQLException("There were " + ret + " rows updated with this records key value.");
322             }
323 
324             return ret;
325         }
326         catch (SQLException e1)
327         {
328             throw e1;
329         }
330         finally
331         {
332             try
333             {
334                 if (stmt != null)
335                 {
336                     stmt.close();
337                 }
338             }
339             catch (SQLException e2)
340             {
341                 throw e2;
342             }
343         }
344     }
345 
346     /***
347      * Saves the data in this Record to the database with an INSERT statement
348      *
349      * @param connection TODO: DOCUMENT ME!
350      *
351      * @return SQL INSERT statement
352      *
353      * @throws DataSetException TODO: DOCUMENT ME!
354      * @throws SQLException TODO: DOCUMENT ME!
355      */
356     private int saveWithInsert(Connection connection)
357             throws DataSetException, SQLException
358     {
359         PreparedStatement stmt = null;
360 
361         try
362         {
363             stmt = connection.prepareStatement(getSaveString());
364 
365             int ps = 1;
366 
367             for (int i = 1; i <= size(); i++)
368             {
369                 Value val = getValue(i);
370 
371                 if (!valueIsClean(i) && !schema().column(i).readOnly())
372                 {
373                     val.setPreparedStatementValue(stmt, ps++);
374                 }
375             }
376 
377             int ret = stmt.executeUpdate();
378 
379             if (((TableDataSet) dataset()).refreshOnSave())
380             {
381                 refresh(dataset().connection());
382             }
383             else
384             {
385                 // Marks all of the values clean since they have now been saved
386                 markRecordClean();
387             }
388 
389             setSaveType(Enums.AFTERINSERT);
390 
391             if (ret > 1)
392             {
393                 throw new SQLException("There were " + ret + " rows inserted with this records key value.");
394             }
395 
396             return ret;
397         }
398         catch (SQLException e1)
399         {
400             throw e1;
401         }
402         finally
403         {
404             try
405             {
406                 if (stmt != null)
407                 {
408                     stmt.close();
409                 }
410             }
411             catch (SQLException e2)
412             {
413                 throw e2;
414             }
415         }
416     }
417 
418     /***
419      * Builds the SQL UPDATE statement for this Record
420      *
421      * @return SQL UPDATE statement
422      *
423      * @throws DataSetException TODO: DOCUMENT ME!
424      */
425     private String getUpdateSaveString()
426             throws DataSetException
427     {
428         KeyDef kd = dataset().keydef();
429 
430         if ((kd == null) || (kd.size() == 0))
431         {
432             throw new DataSetException(
433                 "You must specify KeyDef attributes for this TableDataSet in order to create a Record for update.");
434         }
435         else if (recordIsClean())
436         {
437             throw new DataSetException("You must Record.setValue() on a column before doing an update.");
438         }
439 
440         StringBuffer iss1 = new StringBuffer(256);
441         StringBuffer iss2 = new StringBuffer(256);
442         boolean comma = false;
443 
444         for (int i = 1; i <= size(); i++)
445         {
446             if (!valueIsClean(i) && !schema().column(i).readOnly())
447             {
448                 if (!comma)
449                 {
450                     iss1.append(schema().column(i).name());
451                     iss1.append(" = ?");
452                     comma = true;
453                 }
454                 else
455                 {
456                     iss1.append(", ");
457                     iss1.append(schema().column(i).name());
458                     iss1.append(" = ?");
459                 }
460             }
461         }
462 
463         comma = false;
464 
465         for (int i = 1; i <= kd.size(); i++)
466         {
467             String attrib = kd.getAttrib(i);
468 
469             if (!valueIsClean(schema().index(attrib)))
470             {
471                 throw new DataSetException("The value for column '" + attrib + "' is a key value and cannot be updated.");
472             }
473 
474             if (!comma)
475             {
476                 iss2.append(attrib);
477                 iss2.append(" = ?");
478                 comma = true;
479             }
480             else
481             {
482                 iss2.append(" AND ");
483                 iss2.append(attrib);
484                 iss2.append(" = ?");
485             }
486         }
487 
488         return "UPDATE " + schema().tableName() + " SET " + iss1.toString() + " WHERE " + iss2.toString();
489     }
490 
491     /***
492      * Builds the SQL DELETE statement for this Record
493      *
494      * @return SQL DELETE statement
495      *
496      * @throws DataSetException TODO: DOCUMENT ME!
497      */
498     private String getDeleteSaveString()
499             throws DataSetException
500     {
501         KeyDef kd = dataset().keydef();
502 
503         if ((kd == null) || (kd.size() == 0))
504         {
505             throw new DataSetException("You must specify KeyDef attributes for this TableDataSet in order to delete a Record.");
506         }
507 
508         StringBuffer iss1 = new StringBuffer(256);
509 
510         boolean comma = false;
511 
512         for (int i = 1; i <= kd.size(); i++)
513         {
514             if (!comma)
515             {
516                 iss1.append(kd.getAttrib(i));
517                 iss1.append(" = ?");
518                 comma = true;
519             }
520             else
521             {
522                 iss1.append(" AND ");
523                 iss1.append(kd.getAttrib(i));
524                 iss1.append(" = ? ");
525             }
526         }
527 
528         return "DELETE FROM " + schema().tableName() + " WHERE " + iss1.toString();
529     }
530 
531     /***
532      * Builds the SQL INSERT statement for this Record
533      *
534      * @return SQL INSERT statement
535      *
536      * @throws DataSetException TODO: DOCUMENT ME!
537      */
538     private String getInsertSaveString()
539             throws DataSetException
540     {
541         StringBuffer iss1 = new StringBuffer(256);
542         StringBuffer iss2 = new StringBuffer(256);
543 
544         boolean comma = false;
545 
546         for (int i = 1; i <= size(); i++)
547         {
548             if (!valueIsClean(i) && !schema().column(i).readOnly())
549             {
550                 if (!comma)
551                 {
552                     iss1.append(schema().column(i).name());
553                     iss2.append("?");
554                     comma = true;
555                 }
556                 else
557                 {
558                     iss1.append(", " + schema().column(i).name());
559                     iss2.append(", ?");
560                 }
561             }
562         }
563 
564         return "INSERT INTO " + schema().tableName() + " ( " + iss1.toString() + " ) VALUES ( " + iss2.toString() + " )";
565     }
566 
567     /*
568      *       private Hashtable getAffectedColumns()
569      *         throws DataSetException
570      *       {
571      *               Hashtable affectedColumns = new Hashtable ( size() );
572      *               for ( int i = 1; i <= size(); i++ )
573      *         {
574      *           if ( valueIsClean(i) == false )
575      *             affectedColumns.put ( (Object) new Integer(i) , (Object) schema().getColumns()[i].name() );
576      *         }
577      *               return affectedColumns;
578      *       }
579      */
580 
581     /***
582      * Gets the appropriate SQL string for this record.
583      *
584      * @return SQL string
585      *
586      * @throws DataSetException TODO: DOCUMENT ME!
587      */
588     public String getSaveString()
589             throws DataSetException
590     {
591         if (toBeSavedWithInsert())
592         {
593             return getInsertSaveString();
594         }
595         else if (toBeSavedWithUpdate())
596         {
597             return getUpdateSaveString();
598         }
599         else if (toBeSavedWithDelete())
600         {
601             return getDeleteSaveString();
602         }
603         else
604         {
605             throw new DataSetException("Not able to return save string: " + this.saveType);
606         }
607     }
608 
609     /***
610      * gets the value at index i
611      *
612      * @param i TODO: DOCUMENT ME!
613      *
614      * @return the Value object at index i
615      *
616      * @throws DataSetException TODO: DOCUMENT ME!
617      */
618     public Value getValue(int i)
619             throws DataSetException
620     {
621         if (i == 0)
622         {
623             throw new DataSetException("Values are 1 based!");
624         }
625         else if (i > size())
626         {
627             throw new DataSetException("Only " + size() + " columns exist!");
628         }
629         else if (values[i] == null)
630         {
631             throw new DataSetException("No values for the requested column!");
632         }
633 
634         return values[i];
635     }
636 
637     /***
638      * TODO: DOCUMENT ME!
639      *
640      * @param columnName TODO: DOCUMENT ME!
641      *
642      * @return TODO: DOCUMENT ME!
643      *
644      * @throws DataSetException TODO: DOCUMENT ME!
645      */
646     public Value getValue(String columnName)
647             throws DataSetException
648     {
649         return getValue(schema().index(columnName));
650     }
651 
652     /***
653      * the number of columns in this object
654      *
655      * @return the number of columns in this object
656      */
657     public int size()
658     {
659         return numberOfColumns;
660     }
661 
662     /***
663      * whether or not this Record is to be saved with an SQL insert statement
664      *
665      * @return true if saved with insert
666      */
667     public boolean toBeSavedWithInsert()
668     {
669         return (this.saveType == Enums.INSERT) ? true : false;
670     }
671 
672     /***
673      * whether or not this Record is to be saved with an SQL update statement
674      *
675      * @return true if saved with update
676      */
677     public boolean toBeSavedWithUpdate()
678     {
679         return (this.saveType == Enums.UPDATE) ? true : false;
680     }
681 
682     /***
683      * whether or not this Record is to be saved with an SQL delete statement
684      *
685      * @return true if saved with delete
686      */
687     public boolean toBeSavedWithDelete()
688     {
689         return (this.saveType == Enums.DELETE) ? true : false;
690     }
691 
692     /***
693      * Marks all the values in this record as clean.
694      *
695      * @throws DataSetException TODO: DOCUMENT ME!
696      */
697     public void markRecordClean()
698             throws DataSetException
699     {
700         for (int i = 1; i <= size(); i++)
701         {
702             markValueClean(i);
703         }
704     }
705 
706     /***
707      * Marks this record to be inserted when a save is executed.
708      *
709      * @throws DataSetException TODO: DOCUMENT ME!
710      */
711     public void markForInsert()
712             throws DataSetException
713     {
714         if (dataset() instanceof QueryDataSet)
715         {
716             throw new DataSetException("You cannot mark a record in a QueryDataSet for insert");
717         }
718 
719         setSaveType(Enums.INSERT);
720     }
721 
722     /***
723      * Marks this record to be updated when a save is executed.
724      *
725      * @throws DataSetException TODO: DOCUMENT ME!
726      */
727     public void markForUpdate()
728             throws DataSetException
729     {
730         if (dataset() instanceof QueryDataSet)
731         {
732             throw new DataSetException("You cannot mark a record in a QueryDataSet for update");
733         }
734 
735         setSaveType(Enums.UPDATE);
736     }
737 
738     /***
739      * Marks this record to be deleted when a save is executed.
740      *
741      * @return TODO: DOCUMENT ME!
742      *
743      * @throws DataSetException TODO: DOCUMENT ME!
744      */
745     public Record markToBeDeleted()
746             throws DataSetException
747     {
748         if (dataset() instanceof QueryDataSet)
749         {
750             throw new DataSetException("You cannot mark a record in a QueryDataSet for deletion");
751         }
752 
753         setSaveType(Enums.DELETE);
754 
755         return this;
756     }
757 
758     /***
759      * Unmarks a record that has been marked for deletion.
760      *
761      * <P>
762      * WARNING: You must reset the save type before trying to save this record again.
763      * </p>
764      *
765      * @return TODO: DOCUMENT ME!
766      *
767      * @throws DataSetException TODO: DOCUMENT ME!
768      *
769      * @see #markForUpdate()
770      * @see #markForInsert()
771      * @see #markToBeDeleted()
772      */
773     public Record unmarkToBeDeleted()
774             throws DataSetException
775     {
776         if (this.saveType == Enums.ZOMBIE)
777         {
778             throw new DataSetException("This record has already been deleted!");
779         }
780 
781         setSaveType(Enums.UNKNOWN);
782 
783         return this;
784     }
785 
786     /***
787      * marks a value at a given position as clean.
788      *
789      * @param pos TODO: DOCUMENT ME!
790      *
791      * @throws DataSetException TODO: DOCUMENT ME!
792      */
793     public void markValueClean(int pos)
794             throws DataSetException
795     {
796         if (pos == 0)
797         {
798             throw new DataSetException("Value position must be greater than 0.");
799         }
800         else if (pos > size())
801         {
802             throw new DataSetException("Value position is greater than number of values.");
803         }
804 
805         this.isClean[pos] = true;
806     }
807 
808     /***
809      * marks a value with a given column name as clean.
810      *
811      * @param columnName TODO: DOCUMENT ME!
812      *
813      * @throws DataSetException TODO: DOCUMENT ME!
814      */
815     public void markValueClean(String columnName)
816             throws DataSetException
817     {
818         markValueClean(schema().index(columnName));
819     }
820 
821     /***
822      * marks a value at a given position as dirty.
823      *
824      * @param pos TODO: DOCUMENT ME!
825      *
826      * @throws DataSetException TODO: DOCUMENT ME!
827      */
828     public void markValueDirty(int pos)
829             throws DataSetException
830     {
831         if (pos == 0)
832         {
833             throw new DataSetException("Value position must be greater than 0.");
834         }
835         else if (pos > size())
836         {
837             throw new DataSetException("Value position is greater than number of values.");
838         }
839 
840         this.isClean[pos] = false;
841     }
842 
843     /***
844      * marks a value with a given column name as dirty.
845      *
846      * @param columnName TODO: DOCUMENT ME!
847      *
848      * @throws DataSetException TODO: DOCUMENT ME!
849      */
850     public void markValueDirty(String columnName)
851             throws DataSetException
852     {
853         markValueDirty(schema().index(columnName));
854     }
855 
856     /***
857      * sets the internal save type as one of the defined privates (ie: ZOMBIE)
858      *
859      * @param type TODO: DOCUMENT ME!
860      */
861     void setSaveType(int type)
862     {
863         this.saveType = type;
864     }
865 
866     /***
867      * gets the internal save type as one of the defined privates (ie: ZOMBIE)
868      *
869      * @return TODO: DOCUMENT ME!
870      */
871     int getSaveType()
872     {
873         return this.saveType;
874     }
875 
876     /***
877      * sets the value at pos with a BigDecimal
878      *
879      * @param pos TODO: DOCUMENT ME!
880      * @param value TODO: DOCUMENT ME!
881      *
882      * @return TODO: DOCUMENT ME!
883      *
884      * @throws DataSetException TODO: DOCUMENT ME!
885      */
886     public Record setValue(int pos, BigDecimal value)
887             throws DataSetException
888     {
889         this.values[pos].setValue(value);
890         markValueDirty(pos);
891 
892         return this;
893     }
894 
895     /***
896      * sets the value at pos with a boolean
897      *
898      * @param pos TODO: DOCUMENT ME!
899      * @param value TODO: DOCUMENT ME!
900      *
901      * @return TODO: DOCUMENT ME!
902      *
903      * @throws DataSetException TODO: DOCUMENT ME!
904      */
905     public Record setValue(int pos, boolean value)
906             throws DataSetException
907     {
908         this.values[pos].setValue(new Boolean(value));
909         markValueDirty(pos);
910 
911         return this;
912     }
913 
914     /***
915      * sets the value at pos with a byte[]
916      *
917      * @param pos TODO: DOCUMENT ME!
918      * @param value TODO: DOCUMENT ME!
919      *
920      * @return TODO: DOCUMENT ME!
921      *
922      * @throws DataSetException TODO: DOCUMENT ME!
923      */
924     public Record setValue(int pos, byte [] value)
925             throws DataSetException
926     {
927         this.values[pos].setValue(value);
928         markValueDirty(pos);
929 
930         return this;
931     }
932 
933     /***
934      * sets the value at pos with a java.util.Date
935      *
936      * @param pos TODO: DOCUMENT ME!
937      * @param value TODO: DOCUMENT ME!
938      *
939      * @return TODO: DOCUMENT ME!
940      *
941      * @throws DataSetException TODO: DOCUMENT ME!
942      */
943     public Record setValue(int pos, java.util.Date value)
944             throws DataSetException
945     {
946         this.values[pos].setValue(value);
947         markValueDirty(pos);
948 
949         return this;
950     }
951 
952     /***
953      * sets the value at pos with a java.sql.Date
954      *
955      * @param pos TODO: DOCUMENT ME!
956      * @param value TODO: DOCUMENT ME!
957      *
958      * @return TODO: DOCUMENT ME!
959      *
960      * @throws DataSetException TODO: DOCUMENT ME!
961      */
962     public Record setValue(int pos, java.sql.Date value)
963             throws DataSetException
964     {
965         this.values[pos].setValue(value);
966         markValueDirty(pos);
967 
968         return this;
969     }
970 
971     /***
972      * sets the value at pos with a double
973      *
974      * @param pos TODO: DOCUMENT ME!
975      * @param value TODO: DOCUMENT ME!
976      *
977      * @return TODO: DOCUMENT ME!
978      *
979      * @throws DataSetException TODO: DOCUMENT ME!
980      */
981     public Record setValue(int pos, double value)
982             throws DataSetException
983     {
984         this.values[pos].setValue(new Double(value));
985         markValueDirty(pos);
986 
987         return this;
988     }
989 
990     /***
991      * sets the value at pos with a float
992      *
993      * @param pos TODO: DOCUMENT ME!
994      * @param value TODO: DOCUMENT ME!
995      *
996      * @return TODO: DOCUMENT ME!
997      *
998      * @throws DataSetException TODO: DOCUMENT ME!
999      */
1000     public Record setValue(int pos, float value)
1001             throws DataSetException
1002     {
1003         this.values[pos].setValue(new Float(value));
1004         markValueDirty(pos);
1005 
1006         return this;
1007     }
1008 
1009     /***
1010      * sets the value at pos with a int
1011      *
1012      * @param pos TODO: DOCUMENT ME!
1013      * @param value TODO: DOCUMENT ME!
1014      *
1015      * @return TODO: DOCUMENT ME!
1016      *
1017      * @throws DataSetException TODO: DOCUMENT ME!
1018      */
1019     public Record setValue(int pos, int value)
1020             throws DataSetException
1021     {
1022         this.values[pos].setValue(new Integer(value));
1023         markValueDirty(pos);
1024 
1025         return this;
1026     }
1027 
1028     /***
1029      * sets the value at pos with a long
1030      *
1031      * @param pos TODO: DOCUMENT ME!
1032      * @param value TODO: DOCUMENT ME!
1033      *
1034      * @return TODO: DOCUMENT ME!
1035      *
1036      * @throws DataSetException TODO: DOCUMENT ME!
1037      */
1038     public Record setValue(int pos, long value)
1039             throws DataSetException
1040     {
1041         this.values[pos].setValue(new Long(value));
1042         markValueDirty(pos);
1043 
1044         return this;
1045     }
1046 
1047     /***
1048      * sets the value at pos with a String
1049      *
1050      * @param pos TODO: DOCUMENT ME!
1051      * @param value TODO: DOCUMENT ME!
1052      *
1053      * @return TODO: DOCUMENT ME!
1054      *
1055      * @throws DataSetException TODO: DOCUMENT ME!
1056      */
1057     public Record setValue(int pos, String value)
1058             throws DataSetException
1059     {
1060         this.values[pos].setValue(value);
1061         markValueDirty(pos);
1062 
1063         return this;
1064     }
1065 
1066     /***
1067      * sets the value at pos with a java.sql.Time
1068      *
1069      * @param pos TODO: DOCUMENT ME!
1070      * @param value TODO: DOCUMENT ME!
1071      *
1072      * @return TODO: DOCUMENT ME!
1073      *
1074      * @throws DataSetException TODO: DOCUMENT ME!
1075      */
1076     public Record setValue(int pos, java.sql.Time value)
1077             throws DataSetException
1078     {
1079         this.values[pos].setValue(value);
1080         markValueDirty(pos);
1081 
1082         return this;
1083     }
1084 
1085     /***
1086      * sets the value at pos with a java.sql.Timestamp
1087      *
1088      * @param pos TODO: DOCUMENT ME!
1089      * @param value TODO: DOCUMENT ME!
1090      *
1091      * @return TODO: DOCUMENT ME!
1092      *
1093      * @throws DataSetException TODO: DOCUMENT ME!
1094      */
1095     public Record setValue(int pos, java.sql.Timestamp value)
1096             throws DataSetException
1097     {
1098         this.values[pos].setValue(value);
1099         markValueDirty(pos);
1100 
1101         return this;
1102     }
1103 
1104     /***
1105      * sets the value at pos with a Value
1106      *
1107      * @param pos TODO: DOCUMENT ME!
1108      * @param value TODO: DOCUMENT ME!
1109      *
1110      * @return TODO: DOCUMENT ME!
1111      *
1112      * @throws DataSetException TODO: DOCUMENT ME!
1113      */
1114     public Record setValue(int pos, Value value)
1115             throws DataSetException
1116     {
1117         this.values[pos].setValue(value.getValue());
1118         markValueDirty(pos);
1119 
1120         return this;
1121     }
1122 
1123     /***
1124      * sets the value at column name with a BigDecimal
1125      *
1126      * @param columnName TODO: DOCUMENT ME!
1127      * @param value TODO: DOCUMENT ME!
1128      *
1129      * @return TODO: DOCUMENT ME!
1130      *
1131      * @throws DataSetException TODO: DOCUMENT ME!
1132      */
1133     public Record setValue(String columnName, BigDecimal value)
1134             throws DataSetException
1135     {
1136         setValue(schema().index(columnName), value);
1137 
1138         return this;
1139     }
1140 
1141     /***
1142      * sets the value at column name with a boolean
1143      *
1144      * @param columnName TODO: DOCUMENT ME!
1145      * @param value TODO: DOCUMENT ME!
1146      *
1147      * @return TODO: DOCUMENT ME!
1148      *
1149      * @throws DataSetException TODO: DOCUMENT ME!
1150      */
1151     public Record setValue(String columnName, boolean value)
1152             throws DataSetException
1153     {
1154         setValue(schema().index(columnName), value);
1155 
1156         return this;
1157     }
1158 
1159     /***
1160      * sets the value at column name with a byte[]
1161      *
1162      * @param columnName TODO: DOCUMENT ME!
1163      * @param value TODO: DOCUMENT ME!
1164      *
1165      * @return TODO: DOCUMENT ME!
1166      *
1167      * @throws DataSetException TODO: DOCUMENT ME!
1168      */
1169     public Record setValue(String columnName, byte [] value)
1170             throws DataSetException
1171     {
1172         setValue(schema().index(columnName), value);
1173 
1174         return this;
1175     }
1176 
1177     /***
1178      * sets the value at column name with a java.util.Date
1179      *
1180      * @param columnName TODO: DOCUMENT ME!
1181      * @param value TODO: DOCUMENT ME!
1182      *
1183      * @return TODO: DOCUMENT ME!
1184      *
1185      * @throws DataSetException TODO: DOCUMENT ME!
1186      */
1187     public Record setValue(String columnName, java.util.Date value)
1188             throws DataSetException
1189     {
1190         setValue(schema().index(columnName), value);
1191 
1192         return this;
1193     }
1194 
1195     /***
1196      * sets the value at column name with a java.sql.Date
1197      *
1198      * @param columnName TODO: DOCUMENT ME!
1199      * @param value TODO: DOCUMENT ME!
1200      *
1201      * @return TODO: DOCUMENT ME!
1202      *
1203      * @throws DataSetException TODO: DOCUMENT ME!
1204      */
1205     public Record setValue(String columnName, java.sql.Date value)
1206             throws DataSetException
1207     {
1208         setValue(schema().index(columnName), value);
1209 
1210         return this;
1211     }
1212 
1213     /***
1214      * sets the value at column name with a double
1215      *
1216      * @param columnName TODO: DOCUMENT ME!
1217      * @param value TODO: DOCUMENT ME!
1218      *
1219      * @return TODO: DOCUMENT ME!
1220      *
1221      * @throws DataSetException TODO: DOCUMENT ME!
1222      */
1223     public Record setValue(String columnName, double value)
1224             throws DataSetException
1225     {
1226         setValue(schema().index(columnName), value);
1227 
1228         return this;
1229     }
1230 
1231     /***
1232      * sets the value at column name with a float
1233      *
1234      * @param columnName TODO: DOCUMENT ME!
1235      * @param value TODO: DOCUMENT ME!
1236      *
1237      * @return TODO: DOCUMENT ME!
1238      *
1239      * @throws DataSetException TODO: DOCUMENT ME!
1240      */
1241     public Record setValue(String columnName, float value)
1242             throws DataSetException
1243     {
1244         setValue(schema().index(columnName), value);
1245 
1246         return this;
1247     }
1248 
1249     /***
1250      * sets the value at column name with a int
1251      *
1252      * @param columnName TODO: DOCUMENT ME!
1253      * @param value TODO: DOCUMENT ME!
1254      *
1255      * @return TODO: DOCUMENT ME!
1256      *
1257      * @throws DataSetException TODO: DOCUMENT ME!
1258      */
1259     public Record setValue(String columnName, int value)
1260             throws DataSetException
1261     {
1262         setValue(schema().index(columnName), value);
1263 
1264         return this;
1265     }
1266 
1267     /***
1268      * sets the value at column name with a long
1269      *
1270      * @param columnName TODO: DOCUMENT ME!
1271      * @param value TODO: DOCUMENT ME!
1272      *
1273      * @return TODO: DOCUMENT ME!
1274      *
1275      * @throws DataSetException TODO: DOCUMENT ME!
1276      */
1277     public Record setValue(String columnName, long value)
1278             throws DataSetException
1279     {
1280         setValue(schema().index(columnName), value);
1281 
1282         return this;
1283     }
1284 
1285     /***
1286      * sets the value at column name with a String
1287      *
1288      * @param columnName TODO: DOCUMENT ME!
1289      * @param value TODO: DOCUMENT ME!
1290      *
1291      * @return TODO: DOCUMENT ME!
1292      *
1293      * @throws DataSetException TODO: DOCUMENT ME!
1294      */
1295     public Record setValue(String columnName, String value)
1296             throws DataSetException
1297     {
1298         setValue(schema().index(columnName), value);
1299 
1300         return this;
1301     }
1302 
1303     /***
1304      * sets the value at column name with a java.sql.Time
1305      *
1306      * @param columnName TODO: DOCUMENT ME!
1307      * @param value TODO: DOCUMENT ME!
1308      *
1309      * @return TODO: DOCUMENT ME!
1310      *
1311      * @throws DataSetException TODO: DOCUMENT ME!
1312      */
1313     public Record setValue(String columnName, java.sql.Time value)
1314             throws DataSetException
1315     {
1316         setValue(schema().index(columnName), value);
1317 
1318         return this;
1319     }
1320 
1321     /***
1322      * sets the value at column name with a java.sql.Timestamp
1323      *
1324      * @param columnName TODO: DOCUMENT ME!
1325      * @param value TODO: DOCUMENT ME!
1326      *
1327      * @return TODO: DOCUMENT ME!
1328      *
1329      * @throws DataSetException TODO: DOCUMENT ME!
1330      */
1331     public Record setValue(String columnName, java.sql.Timestamp value)
1332             throws DataSetException
1333     {
1334         setValue(schema().index(columnName), value);
1335 
1336         return this;
1337     }
1338 
1339     /***
1340      * sets the value at column name with a Value
1341      *
1342      * @param columnName TODO: DOCUMENT ME!
1343      * @param value TODO: DOCUMENT ME!
1344      *
1345      * @return TODO: DOCUMENT ME!
1346      *
1347      * @throws DataSetException TODO: DOCUMENT ME!
1348      */
1349     public Record setValue(String columnName, Value value)
1350             throws DataSetException
1351     {
1352         setValue(schema().index(columnName), value);
1353 
1354         return this;
1355     }
1356 
1357     /***
1358      * sets the value at pos with a NULL
1359      *
1360      * @param pos TODO: DOCUMENT ME!
1361      *
1362      * @return TODO: DOCUMENT ME!
1363      *
1364      * @throws DataSetException TODO: DOCUMENT ME!
1365      */
1366     public Record setValueNull(int pos)
1367             throws DataSetException
1368     {
1369         if (pos == 0)
1370         {
1371             throw new DataSetException("Value position must be greater than 0.");
1372         }
1373         else if (pos > size())
1374         {
1375             throw new DataSetException("Value position is greater than number of values.");
1376         }
1377 
1378         this.values[pos].setValue(null);
1379         markValueDirty(pos);
1380 
1381         return this;
1382     }
1383 
1384     /***
1385      * sets the value at column name with a NULL
1386      *
1387      * @param columnName TODO: DOCUMENT ME!
1388      *
1389      * @return TODO: DOCUMENT ME!
1390      *
1391      * @throws DataSetException TODO: DOCUMENT ME!
1392      */
1393     public Record setValueNull(String columnName)
1394             throws DataSetException
1395     {
1396         if ((columnName == null) || (columnName.length() == 0))
1397         {
1398             throw new DataSetException("You must specify a column name!");
1399         }
1400 
1401         setValueNull(schema().index(columnName));
1402 
1403         return this;
1404     }
1405 
1406     /***
1407      * Determines if this record is a Zombie. A Zombie is a record that has been deleted from the database, but not yet removed
1408      * from the DataSet.
1409      *
1410      * @return a boolean
1411      */
1412     public boolean isAZombie()
1413     {
1414         return (this.saveType == Enums.ZOMBIE) ? true : false;
1415     }
1416 
1417     /***
1418      * If the record is not clean, needs to be saved with an Update, Delete or Insert, it returns true.
1419      *
1420      * @return boolean
1421      */
1422     public boolean needsToBeSaved()
1423     {
1424         return !isAZombie() || !recordIsClean() || toBeSavedWithUpdate() || toBeSavedWithDelete() || toBeSavedWithInsert();
1425     }
1426 
1427     /***
1428      * Determines whether or not a value stored in the record is clean.
1429      *
1430      * @param i TODO: DOCUMENT ME!
1431      *
1432      * @return true if clean
1433      */
1434     public boolean valueIsClean(int i)
1435     {
1436         return isClean[i];
1437     }
1438 
1439     /***
1440      * Determines whether or not a value stored in the record is clean.
1441      *
1442      * @param column TODO: DOCUMENT ME!
1443      *
1444      * @return true if clean
1445      *
1446      * @throws DataSetException TODO: DOCUMENT ME!
1447      */
1448     boolean valueIsClean(String column)
1449             throws DataSetException
1450     {
1451         return isClean[getValue(column).columnNumber()];
1452     }
1453 
1454     /***
1455      * Goes through all the values in the record to determine if it is clean or not.
1456      *
1457      * @return true if clean
1458      */
1459     public boolean recordIsClean()
1460     {
1461         for (int i = 1; i <= size(); i++)
1462         {
1463             if (!valueIsClean(i))
1464             {
1465                 return false;
1466             }
1467         }
1468 
1469         return true;
1470     }
1471 
1472     /***
1473      * This method refreshes this Record's Value's. It can only be performed on a Record that has not been modified and has been
1474      * created with a TableDataSet and corresponding KeyDef.
1475      *
1476      * @param connection
1477      *
1478      * @exception DataSetException
1479      * @exception SQLException
1480      */
1481     public void refresh(Connection connection)
1482             throws DataSetException, SQLException
1483     {
1484         if (toBeSavedWithDelete())
1485         {
1486             return;
1487         }
1488         else if (toBeSavedWithInsert())
1489         {
1490             throw new DataSetException("There is no way to refresh a record which has been created with addRecord().");
1491         }
1492         else if (dataset() instanceof QueryDataSet)
1493         {
1494             throw new DataSetException("You can only perform a refresh on Records created with a TableDataSet.");
1495         }
1496 
1497         PreparedStatement stmt = null;
1498 
1499         try
1500         {
1501             stmt = connection.prepareStatement(getRefreshQueryString());
1502 
1503             int ps = 1;
1504 
1505             for (int i = 1; i <= dataset().keydef().size(); i++)
1506             {
1507                 Value val = getValue(dataset().keydef().getAttrib(i));
1508 
1509                 if (val.isNull())
1510                 {
1511                     throw new DataSetException("You cannot execute an update with a null value for a KeyDef.");
1512                 }
1513 
1514                 val.setPreparedStatementValue(stmt, ps++);
1515             }
1516 
1517             ResultSet rs = stmt.executeQuery();
1518             rs.next();
1519 
1520             initializeRecord();
1521 
1522             createValues(rs);
1523         }
1524         catch (SQLException e1)
1525         {
1526             throw e1;
1527         }
1528         finally
1529         {
1530             try
1531             {
1532                 if (stmt != null)
1533                 {
1534                     stmt.close();
1535                 }
1536             }
1537             catch (SQLException e2)
1538             {
1539                 throw e2;
1540             }
1541         }
1542     }
1543 
1544     /***
1545      * This builds the SELECT statement in order to refresh the contents of this Record. It depends on a valid KeyDef to exist and
1546      * it must have been created with a TableDataSet.
1547      *
1548      * @return the SELECT string
1549      *
1550      * @exception DataSetException
1551      */
1552     public String getRefreshQueryString()
1553             throws DataSetException
1554     {
1555         if ((dataset().keydef() == null) || (dataset().keydef().size() == 0))
1556         {
1557             throw new DataSetException(
1558                 "You can only perform a getRefreshQueryString on a TableDataSet that was created with a KeyDef.");
1559         }
1560         else if (dataset() instanceof QueryDataSet)
1561         {
1562             throw new DataSetException("You can only perform a getRefreshQueryString on Records created with a TableDataSet.");
1563         }
1564 
1565         StringBuffer iss1 = new StringBuffer(256);
1566         StringBuffer iss2 = new StringBuffer(256);
1567         boolean comma = false;
1568 
1569         for (int i = 1; i <= size(); i++)
1570         {
1571             if (!comma)
1572             {
1573                 iss1.append(schema().column(i).name());
1574                 comma = true;
1575             }
1576             else
1577             {
1578                 iss1.append(", ");
1579                 iss1.append(schema().column(i).name());
1580             }
1581         }
1582 
1583         comma = false;
1584 
1585         for (int i = 1; i <= dataset().keydef().size(); i++)
1586         {
1587             String attrib = dataset().keydef().getAttrib(i);
1588 
1589             if (!valueIsClean(attrib))
1590             {
1591                 throw new DataSetException("You cannot do a refresh from the database if the value "
1592                     + "for a KeyDef column has been changed with a Record.setValue().");
1593             }
1594 
1595             if (!comma)
1596             {
1597                 iss2.append(attrib);
1598                 iss2.append(" = ?");
1599                 comma = true;
1600             }
1601             else
1602             {
1603                 iss2.append(" AND ");
1604                 iss2.append(attrib);
1605                 iss2.append(" = ?");
1606             }
1607         }
1608 
1609         return "SELECT " + iss1.toString() + " FROM " + schema().tableName() + " WHERE " + iss2.toString();
1610     }
1611 
1612     /***
1613      * TODO: DOCUMENT ME!
1614      *
1615      * @throws DataSetException TODO: DOCUMENT ME!
1616      */
1617     public void saveWithoutStatusUpdate()
1618             throws DataSetException
1619     {
1620         throw new DataSetException("Record.saveWithoutStatusUpdate() is not yet implemented.");
1621     }
1622 
1623     /***
1624      * Gets the schema for the parent DataSet
1625      *
1626      * @return the schema for the parent DataSet
1627      *
1628      * @throws DataSetException TODO: DOCUMENT ME!
1629      */
1630     public Schema schema()
1631             throws DataSetException
1632     {
1633         if (dataset() != null)
1634         {
1635             return this.schema;
1636         }
1637         else
1638         {
1639             throw new DataSetException("Internal Error: Record DataSet is null");
1640         }
1641     }
1642 
1643     /***
1644      * Gets the DataSet for this Record
1645      *
1646      * @return the DataSet for this Record
1647      */
1648     public DataSet dataset()
1649     {
1650         return this.parentDataSet;
1651     }
1652 
1653     /***
1654      * Sets the parent DataSet for this record.
1655      *
1656      * @param ds TODO: DOCUMENT ME!
1657      */
1658     void setParentDataSet(DataSet ds)
1659     {
1660         this.parentDataSet = ds;
1661     }
1662 
1663     /***
1664      * return the value of each column as a string. Not yet implemented!
1665      *
1666      * @param valueseparator
1667      * @param maxwidths
1668      *
1669      * @return the formatted string
1670      *
1671      * @exception DataSetException
1672      */
1673     public String asFormattedString(String valueseparator, int [] maxwidths)
1674             throws DataSetException
1675     {
1676         throw new DataSetException("Not yet implemented!");
1677     }
1678 
1679     /***
1680      * This returns a representation of this Record
1681      *
1682      * @return java.lang.String
1683      */
1684     public String toString()
1685     {
1686         try
1687         {
1688             ByteArrayOutputStream bout = new ByteArrayOutputStream();
1689             PrintWriter out = new PrintWriter(bout);
1690             out.print("{");
1691 
1692             for (int i = 1; i <= size(); i++)
1693             {
1694                 out.print("'" + getValue(i).asString() + "'");
1695 
1696                 if (i < size())
1697                 {
1698                     out.print(',');
1699                 }
1700             }
1701 
1702             out.print("}");
1703             out.flush();
1704 
1705             return bout.toString();
1706         }
1707         catch (DataSetException e)
1708         {
1709             return "";
1710         }
1711     }
1712 }