View Javadoc

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