001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.jdbc.dbo;
007    
008    import java.io.*;
009    import java.sql.*;
010    import java.util.*;
011    
012    import fc.jdbc.*;
013    import fc.io.*;
014    import fc.util.*;
015    import fc.web.forms.*;
016    
017    /**
018    Generates java objects that represent tables in a database. Each object
019    represents 1 table in the database.
020    <p>
021    This framework is great for inserting and updating data in various tables
022    and even fetching one or more rows form individual tables.
023    <p>
024    For each table <font color="blue"><tt>foo</tt></font> in our
025    database, the following classes are generated:
026    <ol>
027    <li><font color="blue">class <tt>Foo</tt></font> which contains all
028    columns of table <tt>foo</tt> as fields and represents a row in that table.
029    Accessor (get/set) methods are provided to modify the values of fields
030    in this class (Note, all generated fields are themselves declared private and
031    we always go through accessor methods so we can keep track of various
032    modifications etc.)
033    <li><font color="blue">class <tt>FooMgr</tt></font> which contains
034    "manager" type functions to read, save, create, etc., instances of class
035    Foo from/to the database. The methods of <tt>FooMgr</tt> could
036    equivalently have been implemented as static methods in <tt>Foo</tt>
037    but they have been separated out in a separate manager class to reduce
038    clutter.
039    <p>
040    This framework is <i>not</i> intended to transparently allow
041    arbitrary joins and data from multiple tables. A <i>better</i> way is
042    to use prepared statements directly to run ad-hoc SQL queries
043    including those containing arbitrary joins.
044    <p>
045    However, to somewhat facilitate arbitrary select queries/joins across multiple tables,
046    each generated "Mgr" class has a <font color=blue><code>columns()</code></font>
047    method that returns a list of columns for the corresponding table. For
048    example, in say a Molly Server Page where information from two tables
049    (table1, table2) is displayed from both tables on the same page.
050    <blockquote><pre style="background: #ccccc;">
051    String my_query =  "select "
052      + <font color=blue>table1Mgr.columns()</font> + ", " + <font color=blue>table2.columns()</font>
053      + " from table1, table2 WHERE table1.x = table2.x";
054    
055    PreparedStatement ps = connection.prepareStatement(my_query);
056    ResultSet rs = connection.executeQuery();
057    while (rs.next())
058        {
059        table1 t1 = table1Mgr.getFromRS(rs);  //create a new table1 from the rs
060        table2 t2 = table2Mgr.getFromRS(rs);  //ditto for table2
061        //..use t1 and t2...
062        //....
063        }
064    </pre></blockquote>
065    </ol>
066    <hr>
067    <h2>Configuration</h2>
068    This program uses a user specified configuration file that allows for 
069    many code generation options. This file takes the following 
070    <a href='doc-files/Generate_usage.txt'> configuration options</a>
071    <p>
072    Here is a minimal <a href='doc-files/sample.conf'>sample 
073    configuration</a> file.
074    <hr>
075    <h3>Notes</h3>
076    <b>Note 1</b>: This framework always retrieves and saves data directly to
077    and fro from the database and never caches data internally. This is a
078    design feature and keeps this framework orthogonal to caching
079    issues/implementations. The results returned by the framework can always
080    be cached as needed via say, the  {@link fc.util.cache.Cache} utility
081    class(es).
082    <p>
083    <b>Note 2</b>: <b>MySQL</b> 3.x, 4.x or 5.x does not have true boolean types and
084    silently converts bool types to TINYINT. This wreaks havoc with
085    auto-generated code which creates methods with the wrong signature
086    (TINYINT as opposed to bool).
087    <p>
088    There are 2 approaches to solving this mysql-specific problem: 
089    <blockquote>
090      a) Require all boolean columns to begin with some keyword (say bool_)
091      and if a column begins with this word, then treat it as a boolean,
092      regardless of the type returned by the database meta data.<br><br>
093      b) Treat all TINYINT's as boolean types. This is the approach I have
094      chosen since TINYINT's are NOT portable across databases (for example
095      PostgresQL does not have TINYINT's). Therefore we should not use
096      TINYINT's in physical database models; if booleans are turned into
097      TINYINT's by MySQL then so be it..since that will not clash with any of
098      our modelled types.
099    </blockquote>
100    If the flag <tt>mysqlBooleanHack</tt> is set to <tt>false</tt> in the
101    configuration file, then TINYINT's are <b>not</b> transformed to booleans.
102    There should be no practical need to do this however.
103    <p>
104    <b>Note 3</b>: <b>MySQL</b> allows its tables and columns to start
105    with a numeral. For example: <i>52_weeks</i>, <i>3_col</i>, etc. This is wrong,
106    not-standard and not-supported. From the spec:
107    </p>
108    <blockquote>
109    SQL identifiers and key words must begin with a letter (a-z, but also letters with diacritical marks and non-Latin letters) or an underscore (_). Subsequent characters in an identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($).
110    </blockquote>
111    <p>
112    So compiling these wrongly named tables (which only MySQL and no other
113    database allows) results in a bunch of java compiler errors, which may
114    confuse neophytes into believing that the generator is outputting buggy
115    code. No, the generated code is proper and exactly the way its intended to
116    be. Java variables/classes <b>cannot</b> start with a number. Hence, 
117    compiler errors. So if you <i>must</i> use MySQL, at least don't name your tables
118    with a number.
119    </p>
120    
121    @author hursh jain
122    **/
123    public final class Generate
124    {
125    static final String nl      = IOUtil.LINE_SEP;
126    static final String mgrSuffix   = "Mgr";
127    static final String DBO_BaseClassName =   "fc.jdbc.dbo.DBO";
128    static final String DBO_MgrBaseClassName =  "fc.jdbc.dbo.DBOMgr";
129    
130    java.util.Date    rundate;
131    DatabaseMetaData  md;
132    PropertyMgr     props;
133    SystemLog     log;
134    Connection      con;
135    String        url;
136    DBspecific      dbspecific;
137    File        outputDir;
138    String        catalogName;
139    String        packageName; 
140    String        classVis;
141    String        fieldVis;
142    boolean       accessors;
143    String[]      tableTypesToProcess;
144    List        tableNamesToInclude;
145    Map         tableNamesToIncludeAction;
146    final int     INCLUDE_ANY_PREFIX  = 1, 
147              INCLUDE_ANY_SUFFIX  = 2, 
148              INCLUDE_CONTAINS  = 3,
149              INCLUDE_EXACT     = 4;
150    List        tableNamesToExclude;
151    Map         tableNamesToExcludeAction;
152    PrintWriter     out;
153    NameWrangle     wrangler;
154    int         processedTablesCount;
155    
156    //TODO: These need to come from a i18n resource file
157    //validate errors/messages
158    String validateNull_ErrorMsg = "Error: Required field, please enter a value";
159    String validateIntegerOnly_ErrorMsg = "Error: Please enter only numbers in this field";
160    String validateText_ErrorMsg_MaxSize = "Not enough or too many characters";
161    
162    //list of tables in our database
163    List        tableList;
164    //used as a temp string buffer by various methods, can
165    //be reset by setLength(0);
166    StringBuffer    strbuf = new StringBuffer(2048);
167    
168    //Changed/set for the CURRENT TABLE being processed
169    String        beanClassName;         //FOO
170    String        mgrClassName;          //FOO_Mgr
171    Table       table;                 //foo
172    List        pklist;                //[a, b]
173    List        fklist;                //[b]
174    String        colsCommaDelimString;  //"a, b, c, d, e"
175    String        pkCommaDelimString;    //"a, b"
176    String        fkCommaDelimString;    //"b, c"
177    String        pkFormalParamString;   //"int a, byte b....."
178    
179    //not enabled -- don't think this is good
180    //enabled again
181    boolean       modifiableAutoIncrementColumns;
182    
183    /* 
184    INTERNAL NOTES:
185    - if a new mgr method is added, also update 
186    mgrWriteMethodFields and mgrWriteMethodStats
187    */
188    
189    public Generate(String[] args) throws Exception
190      {
191      log = Log.getDefault();
192      log.printTimestamp(true);
193      log.printRelativeTimestamp(true);
194    
195      //turn on to debug connection manager etc.
196      //log.setLevel(SystemLog.DEBUG);  
197    
198      Args myargs = new Args(args);
199      myargs.setDefaultUsage(this);
200      String conf   = myargs.getRequired("conf");
201    
202      props = new FilePropertyMgr(new File(conf));
203    
204      //default logging level for the rest of our app
205      String loglevel = props.get("log.level"); 
206    
207      if (loglevel != null) {
208        log.setLevel(loglevel);
209        }
210    
211      ConnectionMgr cmgr = new SimpleConnectionMgr(props);  
212    
213      String driver_name = cmgr.getDriver().getClass().getName();
214      DBName dbname = DBName.fromDriver(driver_name);
215      
216      if (dbname == null) {
217        log.bug("dbname=", dbname);
218        log.error("Could not understand the name of the target database. See documentation for more info.");
219        System.exit(1);
220        }
221      
222      dbspecific = (DBspecific) 
223              Class.forName("fc.jdbc.dbo.DB" + dbname.toString().toLowerCase())
224                .newInstance();
225      
226      url = cmgr.getURL();
227      con = cmgr.getConnection(); 
228      md = con.getMetaData(); 
229    
230      rundate = new java.util.Date();
231    
232      String catalogName = props.get("jdbc.catalog");
233      if (catalogName == null) {
234        catalogName = "";
235        }
236    
237      //Generate options  
238      String output_dir = props.getRequired("generate.output.dir"); 
239      outputDir = new File(output_dir);
240    
241      if (! outputDir.isDirectory() || ! outputDir.canWrite())
242        {
243        log.error("Specified output location '" + output_dir + "' is not a directory and/or is not writable");
244        System.exit(1);
245        }
246    
247      if (! outputDir.exists()) 
248        {
249        System.out.print("Output directory: " + 
250                output_dir + " does not exist. Creating..");
251        outputDir.mkdirs();
252        System.out.println("..done");
253        }
254    
255      log.info("Output Directory: ", outputDir);
256    
257      modifiableAutoIncrementColumns = 
258        Boolean.valueOf(
259          props.get("generate.modifiableAutoIncrementColumns", "false")
260          ).booleanValue();
261      
262      packageName = props.get("generate.class_package");
263    
264      accessors = Boolean.valueOf(
265              props.get("generate.accessors", "true")).
266            booleanValue();
267    
268      classVis = props.get("generate.class_vis", "public");
269      fieldVis = (accessors) ?
270              props.get("generate.field_vis", "private") :
271              /* public by default if no accessors */
272              props.get("generate.field_vis", "public");
273    
274    
275      String tabletypes = props.get("target.types_to_process");
276      if (tabletypes != null) 
277        {
278        tableTypesToProcess = tabletypes.split(",\\s*");
279        for (int n=0; n < tableTypesToProcess.length; n++) {
280          tableTypesToProcess[n] = tableTypesToProcess[n].toUpperCase();
281          }
282        }
283    
284      String table_include = props.get("target.tables_to_process");
285      if (table_include != null) 
286        {
287        tableNamesToInclude = new ArrayList();
288        tableNamesToIncludeAction = new HashMap();
289        createActions(table_include, tableNamesToInclude, tableNamesToIncludeAction);
290        log.bug("Table names to include:", tableNamesToInclude);
291        log.bug("Table names to include action:", tableNamesToIncludeAction);
292        }
293        
294      String table_exclude = props.get("target.tables_to_ignore");
295      if (table_exclude != null) 
296        {
297        tableNamesToExclude = new ArrayList();
298        tableNamesToExcludeAction = new HashMap();
299        createActions(table_exclude, tableNamesToExclude, tableNamesToExcludeAction);
300        log.bug("Table names to exclude:", tableNamesToExclude);
301        log.bug("Table names to exclude action:", tableNamesToExcludeAction);
302        }
303    
304    
305      wrangler = new NameWrangle(props, log);
306      
307      Watch w = new Watch();
308      w.start();
309      readTables();
310      generateCode();
311      cmgr.close();
312      System.out.println("Generator processed "
313        + processedTablesCount 
314        + " table"
315        + ((processedTablesCount > 1) ? "s" : "")
316        + " in "
317        + w.timeInSeconds()
318        + " seconds.");
319      }
320    
321    void createActions(String parameter, List names, Map actionMap) throws Exception
322      {
323      String[] temp = parameter.split(",\\s*");
324      for (int n = 0; n < temp.length; n++) 
325        {     
326        //we match table names from database to those specified
327        //in the config file, both are lowercased before matching
328        //trim() in case table_to_process="foo   "     
329        temp[n] = temp[n].trim().toLowerCase();
330    
331        boolean startsWithStar = false, endsWithStar = false;
332        if (temp[n].startsWith("*")) 
333          {
334          if (temp[n].length() == 1) {
335            throw new Exception("Bad option in config file.\nIn line: " + parameter + "\nA star must be a prefix/suffix to a tablename, not standalone.");
336            }
337          temp[n] = temp[n].substring(1, temp[n].length());
338          startsWithStar = true;
339          }
340        if (temp[n].endsWith("*")) 
341          {
342          if (temp[n].length() == 1) {
343            throw new Exception("Bad option in config file.\nIn line: " + parameter + "\nA star must be a prefix/suffix to a tablename, not standalone.");
344            }
345          temp[n] = temp[n].substring(0, temp[n].length()-1);
346          endsWithStar = true;
347          }
348        
349        if (startsWithStar) 
350          {
351          if (endsWithStar) { //both start/end star
352            actionMap.put(temp[n], INCLUDE_CONTAINS);
353            }
354          else{ //start_star
355            actionMap.put(temp[n], INCLUDE_ANY_PREFIX); 
356            }
357          }
358        else {
359          if (endsWithStar) { //end_star
360            actionMap.put(temp[n], INCLUDE_ANY_SUFFIX);
361            }
362          else{ //exact_match
363            actionMap.put(temp[n], INCLUDE_EXACT); 
364            }
365          }
366          
367        names.add(temp[n]);
368        }
369      }
370    
371    boolean matchTable(String tablename, List names, Map actions) throws IOException
372      { 
373      boolean match = false;
374      
375      for (int p = 0; p < names.size(); p++) 
376        {
377        String  target = (String) names.get(p);
378        int   action = (Integer) actions.get(target);
379    
380        //target=foo*, lower_tablename = foo_xxx, foo etc
381        if (action == INCLUDE_ANY_SUFFIX) 
382          {  
383          if (tablename.startsWith(target)) {
384            match = true;
385            break;
386            }
387          }
388        //target=*foo, lower_tablename = xxx_foo, foo etc
389        else if (action == INCLUDE_ANY_PREFIX) 
390          { //*<tablee>
391          if (tablename.endsWith(target)) {
392            match = true;
393            break;
394            }
395          }
396        else if (action == INCLUDE_CONTAINS) 
397          {
398          if (tablename.indexOf(target) != -1) {
399            match = true;
400            break;
401            }         
402          }
403        else if (action == INCLUDE_EXACT) 
404          {
405          if (tablename.equals(target)) {
406            match = true;
407            break;
408            }         
409          }
410        else{
411          throw new IOException("Internal error. Unrecognized action = " + action);
412          }
413        } //for
414      
415      return match;
416      }
417      
418    void readTables() throws IOException, SQLException
419      {
420      tableList = new ArrayList();
421      
422      Table.init(md, log, props, dbspecific);
423    
424      //not useful generally at least with mysql, postgresql
425      String schemaPattern = null;
426      //we get all tables, maybe if this is needed at all
427      //we can add this as a config file option
428      String tableNamePattern = "%";
429      
430      ResultSet rs = md.getTables(catalogName, schemaPattern, 
431                tableNamePattern, tableTypesToProcess);
432      
433      while (rs.next()) 
434        {
435        String tablename = rs.getString("TABLE_NAME");
436        //tablename cannot be null if the driver is unbroken
437        //but can it be an empty string ?
438        if (tablename.intern() == "") {
439          throw new SQLException("The returned tablename was an empty string, looks like the JDBC driver is broken");
440          }
441          
442        String lower_tablename = tablename.toLowerCase();
443    
444        boolean include = false;
445        
446        if (tableNamesToInclude != null) 
447          {
448          //including only certain tables, if it is NOT in
449          //include list we continue to the next table
450          include = matchTable(lower_tablename, tableNamesToInclude, tableNamesToIncludeAction);
451          
452          if (! include) {
453            log.bug("Ignoring table: ", tablename);
454            continue;   
455            }
456          }
457    
458        
459        boolean exclude = false;
460        
461        if (tableNamesToExclude != null) 
462          {
463          exclude = matchTable(lower_tablename, tableNamesToExclude, tableNamesToExcludeAction);  
464          
465          if (exclude) {
466            log.bug("Ignoring table (via exclude): ", tablename);
467            continue;   
468            }
469          }
470    
471        log.info(">>>> Processing table: ", tablename);   
472        processedTablesCount++;
473        
474        String tabletype  = rs.getString("TABLE_TYPE");
475        String remarks    = rs.getString("REMARKS");
476    
477        Table table = new Table(con, 
478                catalogName, schemaPattern, 
479                tablename, tabletype, remarks
480                ); 
481        tableList.add(table);
482        } //~while rs.next
483      } //~process
484    
485    void generateCode() throws IOException, SQLException
486      {
487      for (int n = 0; n < tableList.size(); n++)
488        {
489        table = (Table) tableList.get(n);
490        pklist = (List) table.getPKList();
491        fklist = (List) table.getFKList();
492        
493        //list of pk's for this table as formal parameters
494        //in a generated method, for example:
495        //      int col_a, String col_b, Date col_c
496        int size = pklist.size();
497        StringBuffer buf = new StringBuffer(128);
498        for (int m = 0; m < size; m++)
499          {
500          ColumnData cd = (ColumnData) pklist.get(m);
501          buf.append(cd.getJavaTypeFromSQLType());
502          buf.append(" ");
503          buf.append(cd.getName());
504          if ( m < (size-1))
505            buf.append(", ");
506          }
507        pkFormalParamString = buf.toString();
508        
509        colsCommaDelimString = Table.getListAsString(table.getColumnList());
510        pkCommaDelimString = Table.getListAsString(pklist);
511        fkCommaDelimString = Table.getListAsString(fklist);
512        writeBeanCode();
513        writeMgrCode();
514        }
515      }
516    
517    void writeBeanCode() throws IOException, SQLException
518      {
519      beanClassName = wrangler.getClassName(table.getName());
520      String filename =  beanClassName + ".java";
521      File f = new File(outputDir, filename);
522    
523      /* this ensures that the new file will not have the same case as the
524      old file, jdk1.5 on osx 10.4 and possibly others keep the existing case 
525      of the file if it already exists.
526      */  
527      if (f.exists())
528        f.delete();
529    
530      out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
531    
532      writePackage();
533      writePrologue();
534      writeImports();
535      beanWriteClass();
536      out.close();
537      } 
538    
539    void writeMgrCode() throws IOException, SQLException
540      {
541      mgrClassName = 
542        wrangler.getClassName(table.getName()) + mgrSuffix;
543        
544      String filename = mgrClassName + ".java";
545      File f = new File(outputDir, filename);
546      
547      /* this ensures that the new file will not have the same case as the
548      old file, jdk1.5 on osx 10.4 and possibly others keep the existing case 
549      of the file if it already exists.
550      */  
551      if (f.exists())
552        f.delete();
553        
554      out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
555      
556      writePackage();
557      writePrologue();
558      writeImports();
559      mgrWriteClass();
560      out.close();
561      } 
562      
563    
564    void writePrologue() 
565      {
566      String name = getClass().getName();
567      ol("/*");
568      ol(" * Auto generated on: " + rundate);
569      ol(" * JDBC url: [" + url + "]");
570      ol(" * WARNING: Manual edits will be lost if/when this file is regenerated.");
571      ol(" */");
572      }
573    
574    void writePackage() 
575      {
576      if (packageName != null) 
577        {
578        ol("package " + packageName + ";");
579        ol();
580        }
581      }
582        
583    void writeImports() 
584      {
585      ol("import java.io.*;");
586      ol("import java.math.*;");
587      ol("import java.sql.*;");
588      ol("import java.util.*;");
589      ol();
590      ol("import fc.io.*;");
591      ol("import fc.jdbc.*;");
592      ol("import fc.jdbc.dbo.*;");
593      ol("import fc.util.*;");
594      ol("import fc.web.forms.*;");
595      ol();
596      }
597      
598    
599    /** 
600    returns the comment used for get/set methods and
601    for fields in the bean class 
602    */
603    final HashMap commentMap = new HashMap();
604    String getBeanComment(ColumnData item) throws SQLException
605      {
606      String comment = (String) commentMap.get(item);
607      if (comment != null) 
608        return comment;
609      
610      comment = 
611        "/** " + item.getSQLTypeDriverSpecificName() +
612            " (" + item.getSQLTypeName() + ")"; 
613      if (item.isPK()) {
614        comment += "; PK=yes";
615        }
616      if (item.isFK()) {
617        ColumnData.FKData fkdata = item.getFK();
618        comment += "; FK=yes, refers to: " 
619          + fkdata.getPKTableName() + "." 
620          + fkdata.getPKColName();
621        } 
622      comment += "; Nullable=" + item.isNullable(); 
623      comment += "; AutoInc=" + item.isAutoIncrement();
624      comment += "; MaxSize=" + item.getSize();
625      if (item.hasRemarks()) {
626        comment += "; Remarks: " + item.getRemarks();
627        }
628      comment += " */";
629      
630      commentMap.put(item, comment);
631      return comment;
632      }
633    
634    
635    void beanWriteClass() throws SQLException
636      {
637      ol("/**");
638      o ("Represents a row in the  ");
639      o ( table.getName());
640      ol(" table. ");
641      
642      String remarks = table.getRemarks(); 
643      if ( remarks != null)
644        {
645        ol("<p><b>Table Remarks: </b>");    
646        ol(remarks);
647        }
648    
649      ol("*/");
650      o(classVis + " class ");
651      o(beanClassName); //classname 
652      o(" extends ");
653      ol(DBO_BaseClassName);
654      ol("{");
655        
656      beanWriteConstructor();
657      beanWriteDBFields();
658      beanWriteDBFieldsTracking();
659      beanWriteMiscMethods();
660      beanWriteGetSet();  
661      ol("}");
662      }
663    
664    void beanWriteConstructor() {
665      ol("/* Default constructor */");
666      o(classVis);
667      o(" ");
668      o(beanClassName);
669      ol("()");
670      ol("  {");
671      ol("  this.__isNew  = true;");
672      ol("  }");
673      ol();
674      }
675    
676    void beanWriteDBFields() throws SQLException
677      {
678      List cols = table.getColumnList();
679    
680      ol("/*--------------------- Columns ------------------------*/");
681      //write database fields
682      TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();
683      config.setPrintBorders(false);
684      config.setCellSpacing(1);
685      config.setCellPadding(0);
686      config.setAutoFit(true);
687      TablePrinter p = new TablePrinter(4, out, config);
688      p.startTable();
689      for (int n = 0; n < cols.size(); n++)
690        {
691        ColumnData cd = (ColumnData) cols.get(n);
692        log.bug("Processing column: ", cd);
693        p.startRow();
694        p.printCell(fieldVis);
695        p.printCell(cd.getJavaTypeFromSQLType());
696        p.printCell(cd.getName() + ";");
697        p.printCell(getBeanComment(cd));  
698        p.endRow();
699        }
700      p.endTable();   
701      ol("/*------------------------------------------------------*/");
702      }
703      
704    void beanWriteDBFieldsTracking() throws SQLException
705      {
706      if (pklist.size() > 0) 
707        {
708        ol("/*");
709        ol("Original PK saved here for updates. If a row is retrieved from the database and");
710        ol("the PK value is changed, and then if the object is saved, we need the orignal PK");
711        ol("value to find the row in the db for our update to work."); 
712        ol("*/");
713        for (int n = 0; n < pklist.size(); n++) 
714          {
715          ColumnData cd = (ColumnData) pklist.get(n);
716          o (cd.getJavaTypeFromSQLType());
717          o (" __orig_");
718          o (cd.getName());
719          ol(";");
720          }
721        }
722    
723      ol();
724      ol("    boolean __force_update  = false;");
725      ol("private Map   __extra_data;     ");
726      ol("private boolean __isNew     = false;");
727      ol("private boolean __isModified  = false;");
728    
729      List cols = table.getColumnList();
730      for (int n = 0; n < cols.size(); n++)
731        {
732        //modified column ?
733        ol();
734        ColumnData cd = (ColumnData) cols.get(n);
735        String colname = cd.getName();
736        o("private   boolean __isModified_");
737        o(colname);
738        ol(" = false;");
739        o("protected boolean __isNullInDB_");
740        o(colname);
741        ol(" = false;");
742        o("/**returns <tt>true</tt> if ");
743        o(colname);
744        o(" has changed since it was created/loaded, <tt>false</tt> otherwise");
745        ol("*/");
746        o("public boolean ");
747        o(wrangler.getIsModifiedName(cd));
748        o("() { return this.__isModified_");
749        o(colname);
750        ol("; }");
751        //column null in database ?
752        o("/**returns <tt>true</tt> if ");
753        o(colname);
754        o(" is null in the database");
755        ol("*/");
756        o("public boolean ");
757        o(wrangler.getIsNullInDBName(cd));
758        o("() { return this.__isNullInDB_");
759        o(colname);
760        ol("; }");
761        }
762      }
763    
764    void beanWriteMiscMethods() throws SQLException
765      {
766      ol();
767      //isnew
768      ol();
769      ol("/** returns <tt>true</tt> if this object is newly created and has <b>not</b> been loaded from the database, <tt>false</tt> otherwise */");
770      ol("public boolean isNew() ");
771      ol("  {");
772      ol("  return this.__isNew;");
773      ol("  }");
774        
775      ol();
776      ol("/** Specify <tt>true</tt> to set this object's status as newly created (and not read from a database) */");
777      ol("protected void setNew(boolean val) ");
778      ol("  {");
779      ol("  this.__isNew = val;");
780      ol("  }");
781        
782      //modified
783      ol();
784      ol("/** returns <tt>true</tt> if this object's data (for any field) has changed since it was created/loaded, <tt>false</tt> otherwise */");
785      ol("public boolean isModified() ");
786      ol("  {");
787      ol("  return this.__isModified;");
788      ol("  }");
789      
790      ol();
791      ol("/** Resets the modified status of this object to not-modified");
792      ol("this is useful when loading an object via a prepared statement");
793      ol("(by using various setXXX methods when we do so, we inadvertently");
794      ol("set the modified status of each field to true)");
795      ol("*/"); 
796      ol("void resetModified() ");
797      ol("  {");
798      List collist = table.getColumnList();
799      ol("  this.__isModified = false;");
800      for (int n = 0; n < collist.size(); n++)
801        {
802        ColumnData cd = (ColumnData) collist.get(n);
803        String colname = cd.getName();
804        o(" this.__isModified_");
805        o(colname);
806        ol(" = false;");
807        } 
808      ol("  }");
809          
810      ol();
811      ol("/**");
812      ol("Allows putting arbitrary object-specific data into this object.");
813      ol("This is useful to store additional query-specific columns when performing");
814      ol("arbitrary queries. For example: ");
815      ol("<blockquote><pre>");
816      o ("String query = \"select <font color=blue>1+1 as my_sum, \n\t\tnow() as my_time</font>, ");
817      o (table.getName());
818      o ("Mgr.columns() \n\t\tfrom ");
819      o (table.getName());
820      ol("\";");
821      ol("PreparedStatement ps = con.prepareStatment(query);");
822      ol("ResultSet rs = ps.executeQuery();");
823      ol("List list = new ArrayList();");
824      ol("while (rs.next()) {");
825      o ("  <font color=green>"); o (table.getName()); o ("</font> obj = "); 
826      o(table.getName()); 
827      ol("Mgr.getFromRS(rs);"); 
828      ol("  obj.<font color=blue>putExtraData(\"my_sum\"</font>, rs.getInt(\"my_sum\"));");
829      ol("  obj.<font color=blue>putExtraData(\"my_time\"</font>, rs.getDate(\"my_time\"));");
830      ol("  }");
831      o ("//use the list later on...each <font color=green>");
832      o (table.getName());
833      ol(" </font>object in the list will ");
834      ol("//have the extra data..");
835      ol("</pre></blockquote>");
836      ol("*/");
837      ol("public void putExtraData(Object key, Object value) ");
838      ol("  {");
839      ol("  synchronized (this) {");
840      ol("    if (__extra_data == null) {");
841      ol("      __extra_data = new HashMap();");
842      ol("      }");
843      ol("    }");
844      ol("  __extra_data.put(key, value);");
845      ol("  }");
846    
847      ol();
848      ol("/**");
849      ol("Allows retrieving arbitrary object-specific data from this object.");
850      ol("This data should have been put via the {@link #putExtraData putExtraData} ");
851      ol("method prior to invoking this method");
852      ol("*/");
853      ol("public Object getExtraData(Object key) ");
854      ol("  {");
855      ol("  synchronized (this) {");
856      ol("    if (__extra_data == null) {");
857      ol("      return null;");
858      ol("      }");
859      ol("    }");
860      ol("  return __extra_data.get(key);");
861      ol("  }");
862          
863      //toString    
864      ol();
865      ol("public String toString() ");
866      ol("  {");
867      ol("  final String nl = fc.io.IOUtil.LINE_SEP;");
868      ol("  StringBuffer buf = new StringBuffer(256);");
869      o ("  buf.append(\"Class Name: [");
870      o (beanClassName);
871      ol("]\");");
872      ol("  buf.append(\" [isDiscarded=\").append(this.isDiscarded()).append(\"]\");");
873      ol("  buf.append(\" [isNew=\").append(this.isNew()).append(\"]\");");
874      ol("  buf.append(\" [isModified=\").append(this.isModified()).append(\"]\");");
875      ol("  buf.append(nl);");
876      ol("  buf.append(\"Note: IsNullInDB only meaningful for existing rows (i.e., isNew=false)\");");
877      ol("  buf.append(nl);");
878      ol();
879      ol("  ByteArrayOutputStream out = new ByteArrayOutputStream(768);");
880      ol("  TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();");
881      ol("  config.setPrintBorders(false);");
882      ol("  config.setCellSpacing(1);");
883      ol("  config.setCellPadding(0);");
884      ol("  config.setAutoFit(true);");
885      ol("  TablePrinter p = new TablePrinter(7, new PrintStream(out), config);");
886      ol("  p.startTable();");
887      ol();
888      ol("  p.startRow();");
889      ol("  p.printCell(\"PK\");");
890      ol("  p.printCell(\"FK\");");
891      ol("  p.printCell(\"Field\");");
892      ol("  p.printCell(\"Value\");");
893      ol("  p.printCell(\"isModified\");");
894      ol("  p.printCell(\"isNullinDB\");");
895      ol("  p.printCell(\"isSerial/AutoInc\");"); 
896      ol("  p.endRow();");
897      
898      for (int n = 0; n < collist.size(); n++)
899        {
900        ColumnData cd = (ColumnData) collist.get(n);
901        String isPK   = cd.isPK() ? "x" : "-";
902        String isFK   = "-";
903        if (cd.isFK()) {
904          ColumnData.FKData fk = cd.getFK();
905          isFK = "x [" +  fk.getPKTableName()  + "." + 
906                  fk.getPKColName() + "]"; 
907                  
908          }
909        String colname  = cd.getName();
910        String value  = "String.valueOf(this." + colname + ")";
911        String modified = "(this.__isModified_" + colname + ")";
912        String isnull = "(this.__isNullInDB_" + colname + ")";
913        String isAI   = cd.isAutoIncrement() ? "x" : "-";
914    
915        ol();
916        ol("  p.startRow();");
917        o ("  p.printCell(\""); o(isPK);  ol("\");");
918        o ("  p.printCell(\""); o(isFK);  ol("\");");
919        o ("  p.printCell(\""); o(colname);  ol("\");");
920        o ("  p.printCell(");   o(value);    ol(");");
921        o ("  p.printCell(");   o(modified); ol(" ?\"x\":\"-\");");
922        o ("  p.printCell(");   o(isnull);   ol(" ?\"x\":\"-\");");
923        o ("  p.printCell(\""); o(isAI);  ol("\");");
924        ol("  p.endRow();");
925        }
926      ol();
927      ol("  p.endTable();");
928      ol("  buf.append(out.toString());");
929      ol("  return buf.toString();");
930      ol("  }");
931    
932      ol(); 
933      ol("/**");
934      ol("Returns a map of all fields-&gt;values (as Strings) contained in this");
935        ol("object. This is useful when sending auto converting the object to JSON, etc.");
936      ol("*/");  
937      ol("public Map allFieldsMap() ");
938      ol("  {");
939      ol("  final HashMap m = new HashMap();");
940      for (int n = 0; n < collist.size(); n++)
941        {
942        ColumnData cd = (ColumnData) collist.get(n);
943    
944        o ("  m.put(\"");
945        o (cd.getName());
946        o ("\", ");
947      
948        if (! cd.usesPrimitiveJavaType()) {
949          o ("("); o(cd.getName()); o(" == null ? null : ");
950          }
951    
952        o ("String.valueOf(this."); o(cd.getName()); o(")");
953    
954        if (! cd.usesPrimitiveJavaType()) {
955          o (")");
956          }
957        
958        ol(");");
959        }
960      ol("  return m;");
961      ol("  }");
962      ol();
963      }
964    
965    void beanWriteGetSet() throws SQLException
966      {
967      List cols = table.getColumnList();
968    
969      ol("/* ============== Gets and Sets ============== */");
970      for (int n = 0; n < cols.size(); n++)
971        {
972        ColumnData cd = (ColumnData) cols.get(n);
973        ol();
974        ol(getBeanComment(cd));
975        String colname = cd.getName();
976        o("public ");
977        o(cd.getJavaTypeFromSQLType());
978        o(" ");
979        o(wrangler.getGetName(cd));
980        ol("()  { ");
981        o(" return ");
982        o(colname);
983        ol(";");
984        ol("  }");
985      
986        ol();
987        ol(getBeanComment(cd));
988        if (cd.isAutoIncrement() &&  (! modifiableAutoIncrementColumns)) {
989          //we need to generate set with package access
990          //to set the id when loading the object internally
991          //from a result set
992          o("/* Generating set for ");
993          o(wrangler.getSetName(cd));
994          ol(" with non public access since it's an auto-increment column */");
995          }
996        else {
997          o("public");
998          } 
999        o(" ");
1000        o(beanClassName);
1001        o(" ");
1002        o(wrangler.getSetName(cd));
1003        o("(");
1004        o(cd.getJavaTypeFromSQLType());
1005        o(" ");
1006        o(colname);
1007        ol(") {");
1008        ol("  this." + colname + " = " + colname + ";"); 
1009        ol("  this.__isModified_" + colname + " = true;" );
1010        ol("  this.__isModified = true;" );
1011        ol("  return this;");
1012        ol("  }");
1013        } 
1014      }
1015      
1016    void mgrWriteClass() throws SQLException
1017      {
1018      //classname
1019      ol("/**");
1020      o("Manages various operations on the ");
1021      o(table.getName());
1022      ol(" table. ");
1023      ol("<p>Most methods of this class take a {@link java.sql.Connection Connection}");
1024      ol("as an argument and use that connection to run various queries. ");
1025      ol("The connection parameter is never closed by methods in this class and that connection");
1026      ol("can and should be used again. Methods of this class will also throw a <tt>IllegalArgumentException</tt>");
1027      ol("if the specified connection object is <tt>null</tt>.");
1028      ol();
1029      ol("<p>Thread Safety: Operations on this class are by and large thread safe in that");
1030      ol("multiple threads can call the methods at the same time. However, seperate threads");
1031      ol("should use seperate connection objects when invoking methods of this class.");  
1032      ol("*/");
1033      o(classVis + " final class " + mgrClassName);
1034      o(" extends ");
1035      ol(DBO_MgrBaseClassName);
1036      ol("{");
1037      
1038      mgrWriteFields();
1039      mgrWriteConstructor();
1040      mgrWriteMethods();  
1041      ol("}");
1042      }
1043    
1044    void mgrWriteConstructor()
1045      {
1046      ol();
1047      ol("/** Constructor is private since class is never instantiated */");
1048      o("private ");
1049      o(mgrClassName);
1050      ol("() {");
1051      ol("\t}");
1052      ol();
1053      }
1054    
1055    void mgrWriteFields() throws SQLException
1056      {
1057      ol("/* --- Fields used for collecting usage statistics --- ");
1058      ol("Increments to these don't need to be synchronized since these are");
1059      ol("ints and not longs and memory visibility is not an issue in the");
1060      ol("toString() method (in which these are read).");
1061      ol("*/");
1062      ol("private static int __getall_called = 0;");
1063      ol("private static int __getlimited_called = 0;");
1064      ol("private static int __getbykey_called = 0;");
1065      ol("private static int __getwhere_called = 0;");
1066      ol("private static int __getusing_called = 0;");
1067      ol("private static int __getusing_ps_called = 0;");
1068      ol("private static int __getfromrs_called = 0;");
1069      ol("private static int __save_called = 0;");
1070      ol("private static int __delete_called = 0;");
1071      ol("private static int __deletebykey_called = 0;");
1072      ol("private static int __deletewhere_called = 0;");
1073      ol("private static int __deleteusing_called = 0;");
1074      ol("private static int __count_called = 0;");
1075      ol("private static int __countwhere_called = 0;");
1076      ol("private static int __countusing_called = 0;");
1077      ol("private static int __exists_called = 0;");
1078      ol("/* -------------- end statistics fields -------------- */");
1079      }
1080    
1081    void mgrWriteMethods() throws SQLException
1082      {
1083      //get
1084      mgrWriteMethodGetAll();
1085      mgrWriteMethodGetAllNoClause();
1086      mgrWriteMethodGetLimited();
1087      mgrWriteMethodGetByKey();
1088      mgrWriteMethodGetWhere();
1089      mgrWriteMethodGetUsing();
1090      mgrWriteMethodGetUsingNoClause();
1091      mgrWriteMethodGetUsingPS();
1092      mgrWriteMethodGetColumnNames();
1093      mgrWriteMethodGetColumnNames2();
1094      mgrWriteMethodGetFromRS();
1095      mgrWriteMethodGetFromRS2();
1096      mgrWriteMethodGetFromRS1Table();  
1097      mgrWriteMethodDecodeFromRS();
1098      //save
1099      mgrWriteMethodSave();
1100      mgrWriteMethodUpdate();
1101      //delete
1102      mgrWriteMethodDelete();
1103      mgrWriteMethodDeleteByKey();
1104      mgrWriteMethodDeleteUsing();
1105      mgrWriteMethodDeleteWhere();
1106      //count, exists, misc.
1107      mgrWriteMethodCount();
1108      mgrWriteMethodCountWhere();
1109      mgrWriteMethodCountUsing();
1110      mgrWriteMethodExists();
1111      mgrWriteMethodExistsUsing();
1112      mgrWriteMethodPrepareStatement();
1113      mgrWriteCheckDiscarded();
1114      mgrWriteMethodStats();
1115      mgrWriteMethodToString();
1116      //validation stuff
1117      mgrWriteValidators(); 
1118      }
1119    
1120    /*
1121    We return a list for no particular reason, it may have
1122    been better to return a bean_type[] instead, which would
1123    save typing casts. Testing shows that:
1124    
1125    bean_type[] arr = (bean_type[]) list.toArray(new bean_type[]);
1126    
1127    takes 1-2 ms for a 1000 elements so speed is not an issue 
1128    (although space might be since the list.toArray is newly 
1129    created).
1130    */
1131    final void mgrWriteMethodGetAll()
1132      {
1133      ol();
1134      o("static private final String getAllStmt = \"SELECT "); 
1135      o(colsCommaDelimString);
1136      o(" from ");
1137      o(table.getName());
1138      ol("\";");
1139      ol("/** ");
1140      ol("Returns all rows in the table. Use with care for large tables since");
1141      ol("this method can result in VM out of memory errors. <p>This method");
1142      ol("also takes an optional (can be null) <tt>clause</tt> parameter which");
1143      ol("is sent as is to the database. For example, a clause can be:");
1144      ol("<blockquote><pre>");
1145      ol("order by some_column_name");
1146      ol("</pre> </blockquote>");   
1147      o("@return  a list containing {@link ");
1148      o(beanClassName);
1149      o(" } objects <i>or an empty list</i> if there are no rows in the database");
1150      ol("*/");
1151      ol("public static List getAll(final Connection con, final String clause) throws SQLException");
1152      ol("  {");  
1153      ol("  __getall_called++;");
1154      ol("  final List list = new ArrayList();");
1155      //prepared statement has no parameters to set, used for
1156      //caching advantage only, (thus, no need to clear params)
1157      ol("  final String getAllStmtClaused = (clause == null) ? ");
1158      ol("               getAllStmt : getAllStmt + \" \" + clause;");
1159      ol("  PreparedStatement ps = prepareStatement(con, getAllStmtClaused);");
1160      ol("  log.bug(\"Query to run: \", ps);");
1161      ol("  final ResultSet rs = ps.executeQuery();");
1162      ol("  while (true) {");
1163      ol("    " + beanClassName +  " bean = decodeFromRS(rs);");
1164      ol("    if (bean == null) { break; } ");
1165      ol("    list.add(bean);");
1166      ol("    }");
1167      ol("  rs.close();");
1168      ol("  return list;"); 
1169      ol("  }");
1170      } //~write getall
1171    
1172    final void mgrWriteMethodGetAllNoClause()
1173      {
1174      ol();
1175      ol("/** ");
1176      o ("Convenience method that invokes {@link getAll(Connection, ");
1177      o (beanClassName);
1178      ol(", String) getAll} with an empty additional clause.");
1179      ol("*/");
1180      o ("public static List getAll(final Connection con) ");
1181      ol("throws ValidateException, SQLException");
1182      ol("  {");
1183      ol("  return getAll(con, null);");
1184      ol("  }");
1185      } //~write getUsing
1186    
1187    final void mgrWriteMethodGetLimited()
1188      {
1189      ol();
1190      o("static private final String getLimitedStmt = \"SELECT "); 
1191      o(colsCommaDelimString);
1192      o(" from ");
1193      o(table.getName());
1194      ol("\";");
1195      ol("/** ");
1196      ol("Returns all rows in the table starting from some row number and limited");
1197      ol("by a certain number of rows after that starting row. ");
1198      ol("<p>");
1199      ol("This method takes a required (non-null) <code>order_clause</code>, since when using");
1200      ol("a limit clause, rows must be ordered for the limit to make sense. The");
1201      ol("clause should be of the form <font color=blue>order by ...</font>");
1202      ol("<p>");
1203      ol("The <code>limit</code> specifies the number of rows that will be returned. (those many");
1204      ol("or possibly lesser rows will be returned, if the query itself yields less");
1205      ol("rows).");
1206      ol("<p>");
1207      ol("The <code>offset</code> skips that many rows before returning rows. A zero offset is");
1208      ol("the same as a traditional query with no offset clause, where rows from");
1209      ol("the beginning are returned. If say, offset = 10, then rows starting from");
1210      ol("row 11 will be returned.");
1211      ol("<p>");
1212      ol("The sql-query generated by this method is database specific but will (typically) look like:");
1213      ol("<blockquote><pre>");
1214      ol("select &lt;column_list&gt; from &lt;table&gt; order by &lt;clause&gt; limit 5 offset 10");
1215      ol("</pre> </blockquote>");   
1216      o("@return  a list containing {@link ");
1217      o(beanClassName);
1218      o(" } objects <i>or an empty list</i> if there are no rows in the database");
1219      ol("*/");
1220      ol("public static List getLimited(final Connection con, final String order_clause, int limit, int offset) throws SQLException");
1221      ol("  {");  
1222      ol("  __getlimited_called++;");
1223      ol("  final List list = new ArrayList();");
1224      //prepared statement has no parameters to set, used for
1225      //caching advantage only, (thus, no need to clear params)
1226      ol("  final String tmp = getLimitedStmt + \" \" + order_clause + \" LIMIT \" + limit + \" OFFSET \" + offset;");
1227      ol("  PreparedStatement ps = prepareStatement(con, tmp);");
1228      ol("  log.bug(\"Query to run: \", ps);");
1229      ol("  final ResultSet rs = ps.executeQuery();");
1230      ol("  while (true) {");
1231      ol("    " + beanClassName +  " bean = decodeFromRS(rs);");
1232      ol("    if (bean == null) { break; } ");
1233      ol("    list.add(bean);");
1234      ol("    }");
1235      ol("  rs.close();");
1236      ol("  return list;"); 
1237      ol("  }");
1238      } //~write getlimited
1239    
1240    final void mgrWriteMethodGetByKey() throws SQLException
1241      {
1242      ol();
1243      if (pklist.size() == 0) {
1244        ol("/* getByKey() not implemented since this table does not have any primary keys defined */");
1245        return;
1246        }
1247    
1248      o("static private final String getByPKStmt = \"SELECT "); 
1249      o(colsCommaDelimString);
1250      o(" from ");
1251      o(table.getName());
1252      o(" WHERE ");
1253      o(Table.getPreparedStmtPlaceholders(pklist));
1254      ol("\";");
1255      ol("/** ");
1256      ol("Returns <b>the</b> row corresponding to the specified primary key(s) of this table ");
1257      ol("or <b><tt>null</tt></b> if no row was found.");
1258      ol("<p>This method uses a prepared statement and is safe from SQL injection attacks");
1259      ol("*/");
1260      o("public static ");
1261      o(beanClassName);
1262      o(" getByKey(final Connection con, ");
1263      o(pkFormalParamString);
1264      ol(") throws SQLException");
1265      ol("  {");
1266      ol("  __getbykey_called++;");
1267      ol("  PreparedStatement ps = prepareStatement(con, getByPKStmt);");
1268      ol("  StringBuilder buf = null;");
1269      ol();
1270      for (int n = 0; n < pklist.size(); n++)
1271        {
1272        ColumnData cd = (ColumnData) pklist.get(n); 
1273        if (! cd.usesPrimitiveJavaType())
1274          {
1275          o ("  if ("); o(cd.getName()); ol(" == null) {");
1276          ol("    if (buf == null) { buf = new StringBuilder(); }");    
1277          o ("    buf.append(\"");
1278          o (cd.getName());
1279          ol(" was set to null (but is non-nullable)\").append(IOUtil.LINE_SEP);");
1280          ol("    }");
1281          ol();
1282          }
1283        String varname = cd.getName();
1284        o("\t");
1285        String pos = String.valueOf((n+1));
1286        ol(cd.getPreparedStmtSetMethod("ps.", pos, varname) );
1287        }
1288      
1289      ol("  if (buf != null) {");
1290      ol("    throw new ValidateException(buf.toString());"); 
1291      ol("    }");
1292      ol("  final ResultSet rs = ps.executeQuery();");
1293      ol("  log.bug(\"Query to run: \", ps);");
1294      ol("  " + beanClassName + " bean = decodeFromRS(rs);");
1295      ol("  rs.close();");
1296    //  ol("  ps.clearParameters();");
1297      ol("  return bean;");
1298      ol("  }");
1299      } //~write getbykey
1300      
1301    final void mgrWriteMethodGetWhere() throws SQLException
1302      {
1303      ol();
1304      ol("/** ");
1305      ol("Returns the rows returned by querying the table with the specified");
1306      ol("<tt>WHERE</tt> clause or <i>an empty list</i> if no rows were found.");
1307      ol("(note: the string <tt>\"WHERE\"</tt> does <b>not</b> have to be");
1308      ol("specified in the clause. It is added automatically by this method).");
1309      ol("<p>Queries can use database functions such as: <code>lower()</code>,");
1310      ol("<code>upper()</code>, <code>LIKE</code> etc. For example:");
1311      o ("<pre><blockquote>"); o(table.getName());
1312      ol("Mgr.getWhere(\"lower(col_a) = 'foo'\")");
1313      ol("//compares the lower case value of col_a with the string 'foo'");
1314      ol("</blockquote></pre>");  
1315      ol("<p><b>The \"where\" clause is sent as-is to the database</b>. SQL");
1316      ol("injection attacks are possible if it is created as-is from a <b><u>untrusted</u></b> source.");
1317      ol();
1318      ol("@throws IllegalArgumentException if the specified <tt>where</tt> parameter is null"); 
1319      ol("*/");
1320      ol("public static List getWhere(final Connection con, final String where) throws SQLException");
1321      ol("  {");
1322      ol("  __getwhere_called++;");
1323      ol("  Argcheck.notnull(where, \"the where parameter was null (and should not be null)\");");
1324      ol("  final String where_stmt = \"SELECT " + colsCommaDelimString + " from " + table.getName() + " WHERE \" + where ;");
1325      ol("  Statement stmt = QueryUtil.getRewindableStmt(con);");
1326      ol("  log.bug(\"Query to run: \", stmt, \" \", where_stmt);");
1327      ol("  final List list = new ArrayList();");
1328      ol("  final ResultSet rs = stmt.executeQuery(where_stmt);");
1329      ol("  while (true) {");
1330      ol("    " + beanClassName +  " bean = decodeFromRS(rs);");
1331      ol("    if (bean == null) { break; } ");
1332      ol("    list.add(bean);");
1333      ol("    }");
1334      ol("  stmt.close();");
1335      ol("  return list;"); 
1336      ol("  }");
1337      } //~write getwhere
1338    
1339    final void mgrWriteMethodGetUsing() throws SQLException
1340      {
1341      ol();
1342      ol("/** ");
1343      ol("Returns the rows returned by querying the table with the value of the");
1344      o ("specified <tt>");
1345      o (beanClassName);
1346      ol("</tt> object or <i>an empty list</i> if no rows were found. As many");
1347      ol("fields in <tt>alltypes</tt> can be set as needed and the values of");
1348      ol("all set fields (including fields explicitly set to <tt>null</tt>)");
1349      ol("are then used to perform the query.");
1350      ol("<p>");
1351      ol("This method is often convenient/safer than the {@link #getWhere");
1352      ol("getWhere} method (because the <tt>getWhere</tt> method takes an");
1353      ol("arbitrary query string which has to be properly escaped by the");
1354      ol("user)."); 
1355      ol("<p>Essentially, this method is a more convenient way to use a");
1356      ol("PreparedStatement. Internally, a prepared statement is created and");
1357      ol("it's parameters are set to fields that are set in this object).");
1358      ol("Using PreparedStatements directly is also perfectly fine. For");
1359      ol("example, the following are equivalent. ");
1360      ol("<p> Using a PreparedStatement:");
1361      ol("<blockquote><pre>");
1362      ol("String foo = \"select * from table_foo where x = ? and y = ?\";");
1363      ol("PreparedStatement ps = con.prepareStatement(foo);");
1364      ol("ps.setString(1, \"somevalue\");");
1365      ol("ps.setString(2, \"othervalue\");");
1366      ol("ResultSet rs  = ps.executeUpdate();");
1367      ol("while (rs.next()) {");
1368      ol("    table_foo bean = table_fooMgr.getFromRS(rs);");
1369      ol("    }");
1370      ol("</pre> </blockquote>");
1371      ol("");
1372      ol("Using this method:");
1373      ol("<blockquote><pre>");
1374      ol("table_foo <font color=blue>proto</font> = new table_foo();");
1375      ol("proto.set_x(\"somevalue\"); //compile time safety");
1376      ol("proto.set_y(\"othervalue\");  //compile time safety");
1377      ol("List beans = table_fooMgr.<font color=blue>getUsing(proto)</font>;");
1378      ol("</pre> </blockquote>"); 
1379      ol("<p>This method also takes an <tt>clause</tt> parameter which");
1380      ol("is sent as is to the database. For example, a clause can be:");
1381      ol("<blockquote><pre>");
1382      ol("order by some_column_name");
1383      ol("</pre> </blockquote>"); 
1384      ol("This clause is optional. Specify <tt>null</tt> to not use it at all.");
1385      ol("<p>Note: For a <i>very</i> large number of rows, it may be more");
1386      ol("efficient to use a prepared statement directly (as opposed to using");
1387      ol("this method). In most cases, this is not something to worry about,");
1388      ol("but your mileage may vary...");
1389      ol("*/");
1390      o("public static List getUsing(final Connection con, final ");
1391      o(beanClassName);
1392      ol(" bean, final String clause) throws ValidateException, SQLException");
1393      ol("  {");
1394      ol("  __getusing_called++;");
1395      ol("  Argcheck.notnull(bean, \"the bean parameter was null (and should not be null)\");");
1396      ol("  if (! bean.isModified()) { ");
1397      ol("    throw new ValidateException(\"bean=\" + bean + \" not modified, ignoring query\");");
1398      ol("    }");
1399      ol();
1400      ol("  int count = 0;");
1401      ol("  final StringBuilder buf = new StringBuilder(512);");
1402      ol("  buf.append(\"SELECT " + colsCommaDelimString + " from " +
1403            table.getName() + " WHERE \");");
1404    
1405      List cols = table.getColumnList();
1406      //we allow any col to be set, including pk or partial pk  
1407      List relevant_cols = cols; 
1408    
1409      for (int n = 0; n < cols.size(); n++) 
1410        {
1411        ColumnData cd = (ColumnData) cols.get(n); 
1412      
1413        o ("  if (bean.");
1414        o (wrangler.getIsModifiedName(cd));
1415        ol("()) { ");
1416        if (! cd.usesPrimitiveJavaType()) {
1417          o ("    if (bean."); o(wrangler.getGetName(cd)); ol("() == null) {");
1418          o ("      buf.append(\""); o(cd.getName()); ol(" is NULL and \");");
1419          ol("      }");
1420          ol("    else{");
1421          o ("      buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
1422          ol("      count++;");
1423          ol("      }");
1424          }
1425        else{
1426          o ("    buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
1427          ol("    count++;");
1428          }
1429        ol("    }");
1430        }   
1431      
1432      ol();
1433      //get rid of last "and "
1434      ol("  buf.setLength(buf.length() - 4);"); 
1435      ol();
1436      ol("  if (clause != null) {");
1437      ol("    buf.append(\" \");");
1438      ol("    buf.append(clause);");
1439      ol("    }");
1440      ol();
1441      ol("  final String getUsingPKStmt = buf.toString();");
1442      ol("  PreparedStatement ps = prepareStatement(con, getUsingPKStmt);");
1443    
1444      utilFillPStmtFromList_IfModified_Object(relevant_cols, "\t");
1445    
1446      ol("  log.bug(\"Query to run: \", ps);");
1447      ol("  final List list = new ArrayList();");
1448      ol("  final ResultSet rs = ps.executeQuery();");
1449      ol("  while (true) {");
1450      ol("    " + beanClassName +  " row = decodeFromRS(rs);");
1451      ol("    if (row == null) { break; } ");
1452      ol("    list.add(row);");
1453      ol("    }");
1454      ol("  rs.close();");
1455    //  ol("  ps.clearParameters();");
1456      ol("  return list;"); 
1457      ol("  }");
1458      } //~write getusing
1459    
1460    final void mgrWriteMethodGetUsingNoClause() throws SQLException
1461      {
1462      ol();
1463      ol("/** ");
1464      o ("Convenience method that invokes {@link getUsing(Connection, ");
1465      o (beanClassName);
1466      o (", String) getUsing} with an empty <i><tt>clause</tt></i> parameter.");
1467      ol("*/");
1468      o("public static List getUsing(final Connection con, final ");
1469      o(beanClassName);
1470      ol(" bean) throws ValidateException, SQLException");
1471      ol("  {");
1472      ol("  return getUsing(con, bean, null);");
1473      ol("  }");
1474      } //~write getUsingnoclause
1475    
1476    //the actual method written is called getUsing(..) not getUsingPS(..)
1477    //i.e., getUsing is overloaded
1478    final void mgrWriteMethodGetUsingPS() throws SQLException
1479      {
1480      ol();
1481      ol("/**");
1482      ol("This is a <i>convenience</i> method that runs the specified ");
1483      ol("prepared statement to perform a arbitrary query. For example: ");
1484      ol("<blockquote>");
1485      ol("<pre>");
1486      ol("PreparedStatement <font color=blue>ps</font> = some_tableMgr.prepare(con, ");
1487      ol("    \"select * from some_table where some_column = ?\"");
1488      ol("ps.setString(1, \"foo\");");
1489      ol("List list = fooMgr.<font color=blue>getUsing</font>(con, <font color=blue>ps</font>);");
1490      ol("for (int n = 0; n < list.size(); n++) {");
1491      ol("  sometable t = (sometable) list.get(n);");
1492      ol("  //do something");
1493      ol("  }");
1494      ol("</pre>");
1495      ol("</blockquote>");
1496      ol("The effect of the above is <u>equivalent</u> to the following (larger) block ");
1497      ol("of code:");
1498      ol("<blockquote>");
1499      ol("<pre>");
1500      ol("PreparedStatement <font color=blue>ps</font> = con.prepareStatement(");
1501      ol("  \"select * from sometable where some_column = ?\"");
1502      ol("  );");
1503      ol("ps.setString(1, \"foo\");");
1504      ol("ResultSet rs = <font color=blue>ps.executeQuery()</font>;");
1505      ol("List list = new ArrayList();");
1506      ol("while (rs.next()) {");
1507      ol("  list.add(sometableMgr.<font color=blue>getFromRS(rs)</font>);");
1508      ol("  }");
1509      ol("");
1510      ol("for (int n = 0; n < list.size(); n++) {");
1511      ol("  sometable t = (sometable) list.get(n);");
1512      ol("  //do something");
1513      ol("  }");
1514      ol("</pre>");
1515      ol("</blockquote>");
1516      ol("");
1517      ol("Note: Just as with other get<i>XXX</i> methods, for large amounts of");
1518      ol("rows (say many thousands), it may be more efficient use and iterate");
1519      ol("through a JDBC result set directly.");
1520      ol("*/");
1521      o("public static List getUsing(final Connection con, ");
1522      ol(" final PreparedStatement ps) throws ValidateException, SQLException");
1523      ol("  {");
1524      ol("  __getusing_ps_called++;");
1525      ol("  log.bug(\"Query to run: \", ps);");
1526      ol("  final List list = new ArrayList();");
1527      ol("  final ResultSet rs = ps.executeQuery();");
1528      ol("  while (true) {");
1529      ol("    " + beanClassName +  " row = decodeFromRS(rs);");
1530      ol("    if (row == null) { break; } ");
1531      ol("    list.add(row);");
1532      ol("    }");
1533      ol("  rs.close();");
1534    //  ol("  ps.clearParameters();");
1535      ol("  return list;"); 
1536      ol("  }");
1537      } //~write getusingps
1538    
1539    final void mgrWriteMethodGetColumnNames() throws SQLException
1540      {
1541      ol();
1542      ol("/** ");
1543      o("Returns a <i>comma delimited list</i> of <i>all</i> columns in <tt>");
1544      o(table.getName());
1545      ol("</tt>. These column names are fully qualified, i.e., they contain ");
1546      ol("table name as a prefix to the column name. For example:");
1547      ol("<blockquote><pre>");
1548      ol("<tt>tablename.column1 AS tablename_column1, tablename.column2 AS tablename_column2 ...</tt>");
1549      ol("</pre></blockquote>");
1550      ol("<p>This list is suitable for placing in the column(s) clause of a select query, such as: ");
1551      ol("<blockquote>");
1552      ol("<tt>Single table: select <i><font color=blue>[column_list_A]</font></i> from table_A</tt><br>");
1553      ol("<tt>Join: select <i><font color=blue>[column_list_A], [column_list_B]</font></i> from table_A, table_B</tt>");
1554      ol("</blockquote>");
1555      ol("The ResultSet returned by the query can be used directly or can be passed");
1556      o ("to the {@link #getFromRS getFromRS} method to convert it into a list of <code>");
1557      ol(table.getName());
1558      ol("</code> objects. If the query is a join across multiple tables,");
1559      ol("then the {@link #getFromRS getFromRS} method for each table manager");
1560      ol("can be called on the same ResultSet to retrieve the row object for");
1561      ol("that table.");
1562      ol("Note: the returned list of names has a trailing space, which is good when");
1563      ol("the rest of the query is appended to this list.");
1564      ol("*/");
1565      o("public static String"); 
1566      ol(" columns() throws SQLException");
1567      ol("  {");
1568      o ("  return \"");
1569      //has a trailing space, which is good, we want to return this 
1570      //with a trailing space
1571      o (   table.getFullyQualifiedColumnString()); 
1572      ol("\";");  
1573      ol("  }");  
1574      } //~write columns
1575    
1576    
1577    final void mgrWriteMethodGetColumnNames2() throws SQLException
1578      {
1579      ol();
1580      ol("/** ");
1581      o("Returns a <i>comma delimited list</i> of <i>all</i> columns in <tt>");
1582      o(table.getName());
1583      ol("</tt>. These column names are prefix with the specified prefix, which corresponds to the");
1584      ol("table abbreviation used in the \"AS\" clause. For example:");
1585      ol("<blockquote><pre>");
1586      ol("<tt>xyz.column1 AS xyz_column1, xyz.column2 AS xyz_column2 ...</tt>");
1587      ol("</pre></blockquote>");
1588      ol("<p>This list is suitable for placing in the column(s) clause of a select query, such as: ");
1589      ol("<blockquote>");
1590      ol("<p><b>Note:</b> the \".\" will automatically be appended between the prefix and column name");
1591      ol("so the prefix should not end with a \".\" or \"_\", etc<p>");
1592      ol("<tt>Single table: select <i><font color=blue>[column_list_A]</font></i> from table_A <b>AS</b> xyz</tt><br>");
1593      ol("<tt>Join: select <i><font color=blue>[column_list_A], [column_list_B]</font></i> from table_A <b>AS</b> xyz, table_B <b>AS</b> zzz</tt>");
1594      ol("</blockquote>");
1595      ol("The ResultSet returned by the query can be used directly or can be passed");
1596      o ("to the {@link #getFromRS getFromRS(String)} method to convert it into a list of <code>");
1597      ol(table.getName());
1598      ol("</code> objects. If the query is a join across multiple tables,");
1599      ol("then the {@link #getFromRS getFromRS(String)} method for each table manager");
1600      ol("can be called on the same ResultSet to retrieve the row object for");
1601      ol("that table.");
1602      ol("Note: the returned list of names has a trailing space, which is good when");
1603      ol("the rest of the query is appended to this list.");
1604      ol("*/");
1605      o("public static String"); 
1606      ol(" columns(String prefix) throws SQLException");
1607      ol("  {");
1608      //has a trailing space, which is good, we want to return this 
1609      //with a trailing space
1610      ol(   table.getPrefixQualifiedColumnString()); 
1611      ol("  }");  
1612      } //~write columns
1613    
1614    
1615    
1616    final void mgrWriteMethodGetFromRS() throws SQLException
1617      {
1618      ol();
1619      ol("/** ");
1620      o("Creates and returns a new <tt>"); o(table.getName());  
1621      ol("</tt> object that represents a row from the specified ResultSet. The ResultSet is");
1622      ol("typically obtained via a handwritten query/PreparedStatement. The resulting ");
1623      ol("ResultSet should contain all of the");
1624      ol("column names of table, and this will only happen if the handwritten query had");
1625      ol("a select statement that specified all fields or used a <tt>select <b>*</b>..</tt>");
1626      ol("clause.");
1627      ol("<p>");
1628      ol("In the select clause, we could also be selecting multiple tables. To disambiguate");
1629      ol("between the same field names that may exist in multiple tables, this method ");
1630      ol("also requires that the query should use <font color=blue>fully qualified</font>");
1631      ol("(prefixed with the table name) column names, such as:");
1632      ol("<blockquote><pre>");
1633      ol("<font color=blue>tablename</font>_column1");
1634      ol("<font color=blue>tablename</font>_column2");
1635      ol("...etc.");
1636      ol("</pre></blockquote>");
1637      ol("<p>");
1638      ol("For example:");
1639      ol("<blockquote>");
1640      ol("<code>select <font color=blue>foo</font>.a <b>AS</b> <font color=blue>foo</font>_a, <font color=red>bar</font>.a <b>AS</b> <font color=red>bar</font>_a from <font color=blue>foo</font>, <font color=red>bar</font> where foo.a = bar.a;</code>");
1641      ol("</blockquote>");
1642      ol("The {@link #columns} method conveniently returns a list of column names in fully qualified format ");
1643      ol("and is useful for this purpose.");
1644      ol("<p>Note: This method will read the <i>current</i> row from the specified result set");
1645      ol("and will <b>not</b> move the result set pointer to the next row after the current");
1646      ol("row has been read. The result set should be appropriately positioned [via <tt>rs.next()</tt>]"); 
1647      ol("<i>before</i> calling this method.");
1648      ol();
1649      ol("@return a new {@link ");
1650      o(beanClassName);
1651      o("} object populated with the contents of the next");
1652      ol("    row from the result set or <tt> null </tt> if");
1653      ol("    the ResultSet was empty.");
1654      ol("*/");
1655      o("public static "); 
1656      o(beanClassName);
1657      ol(" getFromRS(final ResultSet rs) throws SQLException");
1658      ol("  {");
1659      ol("  __getfromrs_called++;");
1660      //decode from RS
1661      ol("  Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1662      ol("  boolean hasrow = ! rs.isAfterLast();");
1663      ol("  if (! hasrow) { ");
1664      ol("    return null; ");
1665      ol("    } ");
1666      o ("  ");o(beanClassName);o(" bean = new ");o(beanClassName);ol("();"); 
1667      ol();
1668      List col_list = table.getColumnList();
1669      for (int n = 0; n < col_list.size(); n++)
1670        {
1671        //THIS METHOD USES COLUMN NAMES NOT COLUMN POSITIONS. THIS IS
1672        //-THE- MAIN DIFF BETWEEN THIS AND THE INTERNAL DECODE_FROM_RS METHOD
1673        //  If a column does not exist in the result set and we try to get
1674        //  that column by name, a sql exception (per the jdbc spec,
1675        //  although the spec is not totally clear on this) is allowed
1676        //  to be thrown (for example, the postgres drives does this)... 
1677        //  so we require all columns to be present while decoding a row
1678        //  typically a  select * must be issued. if the same column exists with
1679        //  the same name in more than 2 tables, we have a problem. We therefore
1680        //  require column names to be qualified with tablenames, such as:
1681        //    select foo.a as foo_a, bar.a as bar_a  from foo, bar;
1682        ColumnData cd = (ColumnData) col_list.get(n); 
1683        String setmethod_name = wrangler.getSetName(cd);
1684        o("\tbean.");
1685        o(setmethod_name);
1686        o("( ");
1687        
1688        if (! cd.useBooleanObject()) {
1689          o("rs.");
1690          }
1691        o(cd.getResultSetMethod(table.getName() + "_"));
1692          
1693        ol(" );");  
1694        
1695        if (cd.isPK()) {
1696          o("\tbean.__orig_");
1697          o(cd.getName());
1698          o(" = ");
1699    
1700          if (! cd.useBooleanObject()){
1701            o("rs.");
1702            }
1703          o(cd.getResultSetMethod(table.getName() + "_"));
1704      
1705          ol("; /* save original PK */");
1706          }
1707            
1708        ol("\tif (rs.wasNull()) {");
1709        o("\t\tbean.__isNullInDB_");
1710        o(cd.getName());
1711        ol(" = true;");
1712        ol("\t\t}");
1713        }
1714      ol();
1715      ol("  /* set to true when instantiated new, false when we populate the bean from a resultset */");
1716      ol("  bean.setNew(false);") ; 
1717      ol("  /* it's not modified, just loaded from the database */");
1718      ol("  bean.resetModified();"); 
1719      ol("  return bean;");
1720      ol("  }");
1721      } //~write getfromrs
1722    
1723    
1724    
1725    final void mgrWriteMethodGetFromRS2() throws SQLException
1726      {
1727      ol();
1728      ol("/** ");
1729      o("Creates and returns a new <tt>"); o(table.getName());  
1730      ol("</tt> object that represents a row from the specified ResultSet. The ResultSet is");
1731      ol("typically obtained via a handwritten query/PreparedStatement. The resulting ");
1732      ol("ResultSet should contain all of the");
1733      ol("column names of table, prefixed with the specified <i>prefix</i> argument."); 
1734      ol("a select statement that specified all fields or used a <tt>select <b>*</b>..</tt>");
1735      ol("clause.");
1736      ol("<p>");
1737      ol("In the select clause, we could also be selecting multiple tables. To disambiguate");
1738      ol("between the same field names that may exist in multiple tables, this method ");
1739      ol("also requires that the query should use a <font color=blue>prefix</font>");
1740      ol("(some arbitrary prefix) before column names, such as:");
1741      ol("<blockquote><pre>");
1742      ol("<font color=blue>foo</font>_column1");
1743      ol("<font color=blue>foo</font>_column2");
1744      ol("...etc.");
1745      ol("</pre></blockquote>");
1746      ol("This prefix will typically be the same as the table abbreviation chosen via the <b>AS</b> clause");
1747      ol("If the AS clause is not used, then it is simpler to use the {@link getFromRS(ResultSet)} method instead");
1748      ol("<p><b>Note:</b> the \".\" will automatically be appended between the prefix and column name");
1749      ol("so the prefix should not end with a \".\" or \"_\", etc<p>");
1750      ol("<p>");
1751      ol("For example:");
1752      ol("<blockquote>");
1753      ol("<code>select <font color=blue>XXX</font>.a <b>AS</b> <font color=blue>XXX</font>_a, <font color=red>YYY</font>.a <b>AS</b> <font color=red>YYY</font>_a from <font color=blue>foo as XXX</font>, <font color=red>bar as YYY</font> where foo.a = bar.a;</code>");
1754      ol("</blockquote>");
1755      ol("The {@link #columns} method conveniently returns a list of column names in fully qualified format ");
1756      ol("and is useful for this purpose.");
1757      ol("<p>Note: This method will read the <i>current</i> row from the specified result set");
1758      ol("and will <b>not</b> move the result set pointer to the next row after the current");
1759      ol("row has been read. The result set should be appropriately positioned [via <tt>rs.next()</tt>]"); 
1760      ol("<i>before</i> calling this method.");
1761      ol();
1762      ol("@return a new {@link ");
1763      o(beanClassName);
1764      o("} object populated with the contents of the next");
1765      ol("    row from the result set or <tt> null </tt> if");
1766      ol("    the ResultSet was empty.");
1767      ol("*/");
1768      o("public static "); 
1769      o(beanClassName);
1770      ol(" getFromRS(final ResultSet rs, String prefix) throws SQLException");
1771      ol("  {");
1772      ol("  __getfromrs_called++;");
1773      //decode from RS
1774      ol("  Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1775      ol("  boolean hasrow = ! rs.isAfterLast();");
1776      ol("  if (! hasrow) { ");
1777      ol("    return null; ");
1778      ol("    } ");
1779      o ("  ");o(beanClassName);o(" bean = new ");o(beanClassName);ol("();"); 
1780      ol();
1781      List col_list = table.getColumnList();
1782      for (int n = 0; n < col_list.size(); n++)
1783        {
1784        //THIS METHOD USES COLUMN NAMES NOT COLUMN POSITIONS. THIS IS
1785        //-THE- MAIN DIFF BETWEEN THIS AND THE INTERNAL DECODE_FROM_RS METHOD
1786        //  If a column does not exist in the result set and we try to get
1787        //  that column by name, a sql exception (per the jdbc spec,
1788        //  although the spec is not totally clear on this) is allowed
1789        //  to be thrown (for example, the postgres drives does this)... 
1790        //  so we require all columns to be present while decoding a row
1791        //  typically a  select * must be issued. if the same column exists with
1792        //  the same name in more than 2 tables, we have a problem. We therefore
1793        //  require column names to be qualified with tablenames, such as:
1794        //    select foo.a as foo_a, bar.a as bar_a  from foo, bar;
1795        ColumnData cd = (ColumnData) col_list.get(n); 
1796        String setmethod_name = wrangler.getSetName(cd);
1797        o("\tbean.");
1798        o(setmethod_name);
1799        o("( ");
1800    
1801        if (! cd.useBooleanObject()) {
1802          o("rs.");
1803          }
1804        o(cd.getRuntimeResultSetMethod());
1805      
1806        ol(" );");  
1807        
1808        if (cd.isPK()) {
1809          o("\tbean.__orig_");
1810          o(cd.getName());
1811          o(" = ");
1812    
1813          if (! cd.useBooleanObject()) {
1814            o("rs.");
1815            }
1816          o(cd.getRuntimeResultSetMethod());
1817    
1818          ol("; /* save original PK */");
1819          }
1820            
1821        ol("\tif (rs.wasNull()) {");
1822        o("\t\tbean.__isNullInDB_");
1823        o(cd.getName());
1824        ol(" = true;");
1825        ol("\t\t}");
1826        }
1827      ol();
1828      ol("  /* set to true when instantiated new, false when we populate the bean from a resultset */");
1829      ol("  bean.setNew(false);") ; 
1830      ol("  /* it's not modified, just loaded from the database */");
1831      ol("  bean.resetModified();"); 
1832      ol("  return bean;");
1833      ol("  }");
1834      } //~write getfromrs2
1835    
1836    
1837    final void mgrWriteMethodGetFromRS1Table() throws SQLException
1838      {
1839      ol();
1840      ol("/** ");
1841      o("Creates and returns a new <tt>"); o(table.getName());
1842      ol("</tt> object that represents a row from the specified ResultSet. For this method");
1843      ol("to work properly, the specified ResultSet should contain <b>all</b> (typically via <b>select *");
1844      o ("</b>) of the column names of table.");
1845      o ("<tt>");
1846      o (table.getName());
1847      ol("</tt>.");
1848      ol("<p>");
1849      ol("This method does not prepend the table name to columns when reading data from");
1850      ol("the result set. It is useful when writing a JDBC query by hand that uses a single table");
1851      ol("(no joins) and then converting the returned result set into objects of this");
1852      ol("class. For example:");
1853      ol("<p>");
1854      ol("<code>select a, b, c, c*2 from foo where a = 1;</code>");
1855      ol("<p>");
1856      ol("This method will expect columns to be called <code><i>a, b, c</i></code> (no column aliases) in the returned");
1857      ol("result set. In this example, there is only one table <code>foo</code> so qualifying the column");
1858      ol("names, like <code>foo.a as foo_a</code> is not necessary). Also note, for this method to work properly, the ");
1859      ol("column list<blockquote><code>select <i>a, b, c </i></code> ...</blockquote> should be complete, i.e., contain <i>at least</i> all the columns");
1860      ol("of this table (<i>additional</i> expressions like c*2 are fine). It is slightly less efficient to retrieve all columns");
1861      ol("especially for large tables but to construct a row into an object, we need all the fields. To be safe, use <blockquote><tt>select * ....</tt></blockquote>");
1862      ol("<p>");
1863      ol("Of course, if one needs a subset of columns, one can use the ResultSet directly and forego trying to");
1864      ol("convert a ResultSet row into an corresponding object");
1865      ol("<p> ");
1866      ol("See {@link getFromRS(ResultSet)} which is more useful when writing a JDBC");
1867      ol("query that uses multiple table joins.");
1868      ol("<p>Note: This method will read the <i>current</i> row from the specified result set");
1869      ol("and will <b>not</b> move the result set pointer to the next row after the current");
1870      ol("row has been read. The result set should be appropriately positioned [via <tt>rs.next()</tt>]"); 
1871      ol("<i>before</i> calling this method.");
1872      ol();
1873      o ("@return a new {@link ");
1874      o (beanClassName);
1875      ol("} object populated with the contents of the next");
1876      ol("    row from the result set or <tt> null </tt> if");
1877      ol("    the ResultSet was empty.");
1878      ol("*/");
1879      o ("public static "); 
1880      o (beanClassName);
1881      ol(" getFromRS1Table(final ResultSet rs) throws SQLException");
1882      ol("  {");
1883      ol("  __getfromrs_called++;");
1884      //decode from RS
1885      ol("  Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1886      ol("  boolean hasrow = ! rs.isAfterLast();");
1887      ol("  if (! hasrow) { ");
1888      ol("    return null; ");
1889      ol("    } ");
1890      o ("  ");o(beanClassName);o(" bean = new ");o(beanClassName);ol("();"); 
1891      ol();
1892      List col_list = table.getColumnList();
1893      for (int n = 0; n < col_list.size(); n++)
1894        {
1895        //THIS METHOD USES COLUMN NAMES NOT COLUMN POSITIONS. THIS IS
1896        //-THE- MAIN DIFF BETWEEN THIS AND THE INTERNAL DECODE_FROM_RS METHOD
1897        //  If a column does not exist in the result set and we try to get
1898        //  that column by name, a sql exception (per the jdbc spec,
1899        //  although the spec is not totally clear on this) is allowed
1900        //  to be thrown (for example, the postgres driver does this)... 
1901        //  so we require all columns to be present while decoding a row
1902        //  typically a  select * must be issued. if the same column exists with
1903        //  the same name in more than 2 tables, we have a problem. We therefore
1904        //  require column names to be qualified with tablenames, such as:
1905        //    select foo.a as foo_a, bar.a as bar_a  from foo, bar;
1906        ColumnData cd = (ColumnData) col_list.get(n); 
1907        String setmethod_name = wrangler.getSetName(cd);
1908        o("\tbean.");
1909        o(setmethod_name);
1910        o("( ");
1911    
1912        if (! cd.useBooleanObject()){
1913          o("rs.");
1914          }
1915        o(cd.getResultSetMethod());
1916      
1917        ol(" );");    
1918    
1919        if (cd.isPK()) {
1920          o("\tbean.__orig_");
1921          o(cd.getName());
1922          o(" = ");
1923          
1924          if (! cd.useBooleanObject()) {
1925            o("rs.");
1926            }
1927          o(cd.getResultSetMethod());
1928      
1929          ol("; /* save original PK */");
1930          }
1931            
1932        ol("\tif (rs.wasNull()) {");
1933        o("\t\tbean.__isNullInDB_");
1934        o(cd.getName());
1935        ol(" = true;");
1936        ol("\t\t}");
1937        }
1938      ol();
1939      ol("  /* set to true when instantiated but this should be false");
1940      ol("   whenever we populate the bean from a result set */");
1941      ol("  bean.setNew(false);") ; 
1942      ol("  //it's not modified, just loaded from the database");
1943      ol("  bean.resetModified();"); 
1944      ol("  return bean;");
1945      ol("  }");
1946      } //~write getfromrs
1947      
1948    final void mgrWriteMethodDecodeFromRS() throws SQLException
1949      {
1950      //internal method. decode from RS. moves the result set via rs.next()
1951      ol();
1952      o("private static "); 
1953      o(beanClassName);
1954      ol(" decodeFromRS(final ResultSet rs) throws SQLException");
1955      ol("  {");
1956      ol("  Argcheck.notnull(rs, \"the specified resultset parameter was null\");");
1957      ol("  boolean hasrow = rs.next();");
1958      ol("  if (! hasrow) { ");
1959      ol("    return null; ");
1960      ol("    } ");
1961      ol("  " + beanClassName + " bean = new " + beanClassName + "();"); 
1962      ol();
1963      List col_list = table.getColumnList();
1964      for (int n = 0; n < col_list.size(); n++)
1965        {
1966        ColumnData cd = (ColumnData) col_list.get(n); 
1967        String setmethod_name = wrangler.getSetName(cd);
1968        o("\tbean.");
1969        o(setmethod_name);
1970        o("( ");
1971    
1972        if (! cd.useBooleanObject()){
1973          o("rs.");
1974          }
1975        o(cd.getResultSetMethod());
1976    
1977        ol(" );");  
1978        
1979        if (cd.isPK()) {
1980          o("\tbean.__orig_");
1981          o(cd.getName());
1982          o(" = ");
1983    
1984          if (! cd.useBooleanObject()){
1985            o("rs.");
1986            }
1987          o(cd.getResultSetMethod());
1988    
1989          ol("; /* save original PK */");
1990          }
1991    
1992        ol("\tif (rs.wasNull()) {");
1993        o("\t\tbean.__isNullInDB_");
1994        o(cd.getName());
1995        ol(" = true;");
1996        ol("\t\t}");
1997        ol();
1998        }
1999    
2000      ol();
2001      ol("  /* set to true when newly instantiated but this should be false");
2002      ol("   whenever we populate the bean from a result set */");
2003      ol("  bean.setNew(false);") ; 
2004      ol("  //it's not modified, just loaded from the database");
2005      ol("  bean.resetModified();"); 
2006      ol("  return bean;");
2007      ol("  }");
2008      } //~write decodefromrs
2009      
2010    final void mgrWriteMethodSave() throws SQLException 
2011      {
2012      ColumnData cd = null;
2013      List cols = table.getColumnList();
2014      //we don't insert or update auto-increment columns
2015      //filter those out and put the rest in filtered_cols
2016      List filtered_cols = new ArrayList(); 
2017      List autoinc_cols = new ArrayList();
2018      
2019      for (int n = 0; n < cols.size(); n++)
2020        {
2021        cd = (ColumnData) cols.get(n);  
2022        if (cd.isAutoIncrement()) {
2023          autoinc_cols.add(cd);
2024          if (! modifiableAutoIncrementColumns) {
2025            continue;   //don't add it to the filtered list
2026            }
2027          }
2028        filtered_cols.add(cd);
2029        }
2030    
2031      final int filtered_count = filtered_cols.size();
2032      ol();
2033      ol("/**");
2034      ol("Saves the specified object into the database. If the specified");
2035      ol("object was newly created, then it is <span style=\"font-variant:");
2036      ol("small-caps\">insert</span>'ed into the database, else it is <span ");
2037      ol("style=\"font-variant: small-caps\">update</span>'ed. (this can be");
2038      ol("overriden by the {@link #update update} method). If the object is");
2039      ol("inserted as a new row, then after insertion, the values of");
2040      ol("serial/auto-incremented columns will be automatically available via the");
2041      ol("appropriate getXXX() methods on that object.");
2042      ol("<p>");
2043      ol("<b>NOTE 1:</b> When saving an object, only modified fields are");
2044      ol("saved. Do not rely on default field values (such as null) of newly");
2045      ol("created objects; instead explicitly set the value (including to null");
2046      ol("if needed) of any field that should be saved to the database.");
2047      ol("<p>");
2048      ol("<b>NOTE 2:</b> Once an object is successfully saved, it is discarded");
2049      ol("and cannot be saved again and any attempt to save it again will");
2050      ol("result in a runtime exception. Objects that need to be modified");
2051      ol("again must be re-instantiated or re-populated from the database");
2052      ol("before they can be saved again. (the serial/auto-increment data will still be");
2053      ol("available, discarding only affects the ability to save the object");
2054      ol("again).");
2055      ol("<b>Note 3:</b> <font color='red'>For various reasons/flexiblity, default database values");
2056      ol("for columns <i>other</i> than serial/non auto-increment columns are <b>not</b> available");
2057      ol("in the saved object. To get these values, retrieve the saved object again. (this is what");
2058      ol("we would have to do internally anyway). This is relevant, for example, when a column has");
2059      ol("a default value of a now() timestamp, and we need to get that timestamp after the object");
2060      ol("has been saved</font>");
2061      ol();
2062      ol("@return   the number of rows inserted or updated (typically useful ");
2063      ol("      to see if an update succeeded)");
2064      ol("@throws ValidateException   on a validation error");
2065      ol("@throws SQLException    on some SQL/Database error");
2066      ol("@throws IOException     by the available() method if/when");
2067      ol("              setting a stream for longvar/text types");
2068      ol("*/");
2069      o("public static int save(final Connection con, final ");
2070      o(beanClassName);
2071      //we need IOException for the available() method when
2072      //setting a stream for longvar/text types in our statement
2073      ol(" bean) throws ValidateException, SQLException, IOException");
2074      ol("  {");
2075      ol("  __save_called++;");
2076      ol("  Argcheck.notnull(bean, \"the specified bean parameter was null\");");
2077      ol("  checkDiscarded(bean);");
2078      ol("  if (! bean.isModified()) { ");
2079      ol("    log.warn(\"bean=\" + bean + \" not modified, IGNORING SAVE\");");
2080      ol("    return 0;");
2081      ol("    }");
2082      ol("  PreparedStatement ps = null;");
2083      ol();
2084      //.... insert into table_foo (col_x, col_y) values (?, ?).....
2085      //.... update table_foo set (col_x=? col_y=?) where ....
2086      ol("  boolean inserting_a_row = false;");
2087      ol("  if (bean.isNew() && ! bean.__force_update) ");
2088      //----------------- INSERT ---------------------
2089      ol("    {  //insert new row");
2090      ol("    validateBeforeSaveNew(bean);");
2091      ol("    int count = 0;");
2092      ol("    inserting_a_row = true;");
2093      ol("    final StringBuilder buf = new StringBuilder(512);");
2094      o ("    buf.append(\"INSERT into "); 
2095      o(table.getName());
2096      ol(" (\");");
2097    
2098      for (int n = 0; n < filtered_count; n++) 
2099        {
2100        cd = (ColumnData) filtered_cols.get(n); 
2101        o ("    if (bean.");
2102        o(wrangler.getIsModifiedName(cd));
2103        ol("()) { ");
2104        o("     buf.append(\"");
2105        o(cd.getName());
2106        ol("\").append(\", \");");
2107        ol("      count++;");
2108        ol("      }");
2109        } 
2110      ol();
2111    
2112      ol("    if (count == 0) {");
2113      ol("      throw new ValidateException(\"Cannot save this bean because no column has been modified. Use JDBC directly as needed.\\n\");");
2114      ol("    }");
2115    
2116      //get rid of last ", " [ we need to do it this way
2117      //since we don't know when the last ',' will come since
2118      //any number of columns could have been modified (or not)
2119      ol("    buf.setLength(buf.length() - 2);"); 
2120      ol("    buf.append(\") values (\");");
2121      ol("    for (int n = 0; n < count; n++) { ");
2122      ol("      buf.append(\"?\");");
2123      ol("      if ((n+1) < count)");
2124      ol("        buf.append(\", \");");
2125      ol("      }");
2126      ol("    buf.append(\")\");");
2127      ol();
2128      ol("    final String insertByPKStmt = buf.toString();");
2129      ol("    ps = prepareStatement(con, insertByPKStmt);");
2130      
2131      ol("    /* Insert any changed values into our prepared statement */");
2132      utilFillPStmtFromList_IfModified(filtered_cols, "\t\t");    
2133      ol("    }");
2134      
2135      //-------------------- UPDATE -----------------------
2136      ol("  else //update existing row ");
2137      ol("    {");
2138      if (pklist.size() == 0) {
2139        //we write pknum to shut up compiler about unreachable statements
2140        ol("    int pknum = 0;");
2141        ol("    if (pknum == 0) {");
2142        ol("      throw new ValidateException(");
2143        ol("        \"Cannot update this bean because it has no primary keys.\""); 
2144        ol("        + \"Use JDBC directly to update values to this table.\");");
2145        ol("      }");
2146        } 
2147      ol("    validateBeforeSaveUpdate(bean);");
2148      ol("    int count = 0;");
2149      ol("    final StringBuilder buf = new StringBuilder(512);");
2150      ol("    buf.append(\"UPDATE \");"); 
2151      o ("    buf.append(\"");
2152      o(table.getName());
2153      ol("\");");
2154      ol("    buf.append(\" SET \");");
2155      for (int n = 0; n < filtered_count; n++) 
2156        {
2157        cd = (ColumnData) filtered_cols.get(n); 
2158        o("   if (bean.");
2159        o(wrangler.getIsModifiedName(cd));
2160        ol("()) { ");
2161        o("     buf.append(\"");
2162        o(cd.getName());
2163        o("=?, ");
2164        ol("\");"); 
2165        ol("      count++;");
2166        ol("      }");
2167        } 
2168      ol(); 
2169      
2170      ol("    if (count == 0) {");
2171      ol("      throw new ValidateException(\"Cannot save this bean because no column has been modified. Use JDBC directly as needed.\\n\");");
2172      ol("    }");
2173      
2174      //get rid of last ", " [ we need to do it this way
2175      //since we don't know when the last ',' will come since
2176      //any number of columns could have been modified (or not)
2177      ol("    buf.setLength(buf.length() - 2);"); 
2178      
2179      ol("    buf.append(\" WHERE \");");
2180      o ("    buf.append(\"");
2181      o(Table.getPreparedStmtPlaceholders(pklist));
2182      ol("\");");
2183      
2184      ol("    ps = con.prepareStatement(buf.toString());");
2185      //ol("    log.bug(ps);");   //we write a debug below anyway
2186      ol();
2187      
2188      ol("    /* Insert any changed values into our prepared statement */");
2189      //note we don't have to worry about columns that have
2190      //not modified (including primitive columns for which
2191      //isNullInDB is true)
2192      utilFillPStmtFromList_IfModified(filtered_cols, "\t\t");    
2193      
2194      ol(); 
2195      ol("    /* Set primary keys for the WHERE part of our prepared statement */");
2196      for (int n = 0; n < pklist.size(); n++)
2197        {
2198        cd = (ColumnData) pklist.get(n);  
2199        String getmethod_name = wrangler.getGetName(cd);
2200        String varname = cd.getName();
2201    
2202        o("\t\t");
2203        o(cd.getJavaTypeFromSQLType());
2204        o(" ");
2205        o(varname);
2206        o(" = ");
2207    
2208        //If it is a forced update, then the primary key(s) are always 
2209        //provided as a param (so use that to find the row to update, not
2210        //the cached pk, since that will be null/empty in a new object anyway)
2211        //If it's not a forced update, use cached orig pk value (in case pk 
2212        //itself was changed)
2213        
2214        o("(bean.__force_update) ? ");
2215        o("bean.");
2216        o(getmethod_name);
2217        o("()");
2218        o(" : ");
2219        o("bean.__orig_");
2220        o(cd.getName());
2221        ol(";");
2222      
2223        o("\t\t");
2224        //skip over the if_modified '?' (which are created
2225        //at runtime by the generated code using the 'pos')
2226        //integer
2227        ol(cd.getPreparedStmtSetMethod("ps.", "++pos", varname));
2228        }
2229      ol("\t\t} //~else update;");
2230      ol();
2231      ol("  log.bug(\"Query to run: \", ps);");
2232      ol("  int result = ps.executeUpdate();");
2233    
2234      ol("  if (inserting_a_row) { //get auto increment info");
2235      int autoinc_size = autoinc_cols.size();
2236      if (autoinc_size > 0)
2237        {
2238        ol("    /* Retrieve values from auto-increment columns */");
2239        ol("    ResultSet rs = null; Statement stmt = null;");
2240        ol("    String query = null;");
2241        ol("    boolean found = false;");
2242        ol(); 
2243        for (int n = 0; n < autoinc_size; n++)
2244          {
2245    
2246        //if a auto increment column is modifiable AND was modified,
2247        //then we cannot get a auto increment value for it (since
2248        //we are specifying our own value in that case, the sequence
2249        //on the database is not used)
2250        
2251          o ("    if (bean.");
2252          o(wrangler.getIsModifiedName(cd));
2253          ol("()) { ");
2254          o ("      //column: ");
2255          ol(cd.getName());
2256          ol("      //not getting auto increment value for this column");
2257          ol("      //since not using auto increment, a value was specified manually");
2258          ol("      }");    
2259          ol("    else{");        
2260    
2261          cd = (ColumnData) autoinc_cols.get(n);
2262          String query = dbspecific.getAutoIncrementQuery(cd);
2263          String setmethod_name = wrangler.getSetName(cd);
2264          ol("      stmt = con.createStatement();");
2265          o ("      query = \""); o(query); ol("\";");
2266          ol("      rs = stmt.executeQuery(query);");
2267          ol("      found = rs.next();");
2268          ol("      if (! found) throw new SQLException(\"No last inserted id returned\");");
2269          o ("      bean."); o(setmethod_name); o("( "); 
2270    
2271          if (! cd.useBooleanObject()){
2272            o("rs.");
2273            }
2274          o(cd.getResultSetMethod());
2275      
2276          ol(");");   
2277    
2278          ol("      if (rs.wasNull()) {");
2279          o ("        bean.__isNullInDB_");
2280          o (cd.getName());
2281          ol(" = true;");
2282          ol("        }");
2283          ol("      rs.close();");
2284          ol("      }");
2285          } //~for
2286        ol("    }");
2287        } //~if auto-inc size > 0
2288      else {
2289        ol("    //No auto inc columns in this table");
2290        ol("    }");
2291        } 
2292      ol();
2293      ol("  //discard after saving/updating for safety");
2294      ol("  bean.discard();");
2295      ol("  return result;");
2296      ol("  }");  
2297      }
2298    
2299    final void mgrWriteMethodUpdate() throws SQLException 
2300      {
2301      ol();
2302      if (pklist.size() == 0) {
2303        ol("/* update() method not implemented since this bean has no primary keys. Use JDBC directly.");
2304        return;
2305        }
2306    
2307      ol("/**");
2308      ol("Uses the specified object to update existing data in the database.");
2309      ol("<p>");
2310      ol("Note, the {@link #save save} method automatically saves newly created objects");
2311      ol("as <i>inserts</i> in the database. (and <i>retrieved</i> objects, when");
2312      ol("subsequently modified, are saved as <i>updates</i>).");
2313      ol("<p>");
2314      ol("However, sometimes it is useful to create a <i>new</i> object and then");
2315      ol("use it's data to <i>update</i> an existing row in the database.");
2316      ol("This method need <b>only</b> be called to save a <u>newly</u>");
2317      ol("created object as an <u>update</u> into the database (overriding the");
2318      ol("default action of saving new objects as inserts in the database).");
2319      ol("<p>");
2320      ol("Note, also, a bean can only be updated if the corresponding table it has");
2321      ol("at least one primary key defined. To update tables with no primary keys,");
2322      ol("use JDBC directly.");
2323      ol("<p>");
2324      o("This method takes primary key(s) of {@link ");
2325      o(beanClassName);
2326      ol("} as additional arguments and sets those in the");
2327      ol("specified bean before updating the database (this way the row to update");
2328      ol("can be uniquely identified).");
2329      ol();
2330      ol("@see #save"); 
2331      ol();
2332      ol("@return   the number of rows that were updated (typically useful ");
2333      ol("      to see if an update succeeded)");
2334      ol("@throws ValidateException   on a validation error");
2335      ol("@throws SQLException    on some SQL/Database error");
2336      ol("*/");
2337      o("public static int update(final Connection con, final ");
2338      o(beanClassName);
2339      o(" bean, ");
2340      o(pkFormalParamString); 
2341      ol(") throws ValidateException, SQLException, IOException");
2342      ol("  {");
2343    
2344      for (int n = 0; n < pklist.size(); n++)
2345        {
2346        ColumnData cd = (ColumnData) pklist.get(n); 
2347        String varname = cd.getName(); /* used in pkFormalParamString too*/
2348        o("\tbean.");
2349        o( wrangler.getSetName(cd));
2350        o("(");
2351        o(varname);
2352        o(");");
2353        ol();
2354        }
2355      ol();
2356      ol("  if (bean.isNew()) { /* force update (and not insert) for new bean */");
2357      ol("    bean.__force_update = true;");
2358      ol("    }");
2359      ol("  return save(con, bean);");
2360      ol("  }");
2361      }
2362      
2363    final void mgrWriteMethodDelete() throws SQLException
2364      {
2365      ol();
2366      if (pklist.size() == 0) {
2367        ol("/* delete() not implemented since this table does not have any primary keys defined. */");
2368        return;
2369        }
2370      o("static private final String deleteStmt = \"DELETE "); 
2371      o(" from ");
2372      o(table.getName());
2373      o(" WHERE ");
2374      o(Table.getPreparedStmtPlaceholders(pklist));
2375      ol("\";");
2376      ol("/** ");
2377      o("Deletes this object from the database. ");
2378      ol("<p>");
2379      ol("<b>NOTE 1:</b> Only objects that were retrieved from the database can be deleted. Newly");
2380      ol("created objects cannot be deleted since they do not yet exist in the database.");
2381      ol("Use {@link #deleteByKey deleteByKey} or {@link #deleteWhere deleteWhere} instead");
2382      ol("for arbitrary deletions. <p><b>NOTE 2:</b> Once an object is successfully");
2383      ol("deleted, it is discarded and cannot be deleted again and any attempt to delete");
2384      ol("it again will result in a runtime Exception.");
2385      ol("*/");
2386      o("public static void delete(final Connection con, ");
2387      o(beanClassName);
2388      ol(" bean) throws SQLException");
2389      ol("  {");
2390      ol("  __delete_called++;");
2391      ol("  if (bean.isNew()) {");
2392      ol("    throw new DBOException(\"Cannot delete new objects using this method. Use deleteByKey() or deleteWhere() instead\");");
2393      ol("    }");
2394      ol("  checkDiscarded(bean);");  
2395      ol("  final PreparedStatement ps = prepareStatement(con, deleteStmt);");
2396      //list of pk's for this table
2397      for (int n = 0; n < pklist.size(); n++)
2398        {
2399        ColumnData cd = (ColumnData) pklist.get(n); 
2400        String getmethod_name = wrangler.getGetName(cd);
2401        o("\t");
2402        o(cd.getJavaTypeFromSQLType());
2403        o(" ");
2404        String varname = cd.getName();
2405        o(varname);
2406        o(" = bean.");
2407        o(getmethod_name);
2408        ol("();");
2409        o("\t");
2410        String pos = String.valueOf((n+1));
2411        ol(cd.getPreparedStmtSetMethod("ps.", pos, varname));
2412        }
2413      ol("  log.bug(\"Query to run: \", ps);");
2414      ol("  final int result = ps.executeUpdate();");
2415    //  ol("  ps.clearParameters();");
2416      ol("  if (result != 1) { ");
2417      ol("    throw new DBOException(\"The number of deleted rows was: \" + result + \"; [Should have been 1 row exactly] \");");
2418      ol("    }");
2419      ol("  }");
2420      }
2421      
2422    final void mgrWriteMethodDeleteByKey() throws SQLException
2423      {
2424      ol();
2425      if (pklist.size() == 0) {
2426        ol("/* deleteByKey() not implemented since this table does not have any primary keys defined */");
2427        return;
2428        }
2429    
2430      o("static private final String deleteByPKStmt = \"DELETE "); 
2431      o(" from ");
2432      o(table.getName());
2433      o(" WHERE ");
2434      o(Table.getPreparedStmtPlaceholders(pklist));
2435      ol("\";");
2436      ol("/** ");
2437      o("Deletes the rows with the specified primary key(s) from the database. ");
2438      ol("<p>This method uses a prepared statement and is safe from SQL injection attacks");
2439      ol("*/");
2440      o("public static void deleteByKey(final Connection con, ");
2441      o(pkFormalParamString);
2442      ol(") throws SQLException");
2443      ol("  {");
2444      ol("  __deletebykey_called++;");
2445      ol("  PreparedStatement ps = prepareStatement(con, deleteByPKStmt);");
2446      
2447      for (int n = 0; n < pklist.size(); n++)
2448        {
2449        ColumnData cd = (ColumnData) pklist.get(n); 
2450        String varname = cd.getName();
2451        o("\t");
2452        String pos = String.valueOf((n+1));
2453        ol( cd.getPreparedStmtSetMethod("ps.", pos, varname) );
2454        }
2455      
2456      ol("  log.bug(\"Query to run: \", ps);");
2457      ol("  final int result = ps.executeUpdate();");
2458    //  ol("  ps.clearParameters();");
2459      ol("  if (result != 1) { ");
2460      ol("    throw new DBOException(\"The number of deleted rows was: \" + result + \"; [Should have been 1 row exactly] \");");
2461      ol("    }");
2462      ol("  }");
2463      } //~write delete by key
2464      
2465    final void mgrWriteMethodDeleteWhere()
2466      {
2467      ol();
2468      ol("/** ");
2469      ol("Deletes the rows with the specified where clause. <p><b>The");
2470      ol("where clause is sent as-is to the database and SQL injection");
2471      ol("attacks are possible if it is created as-is from a untrusted");
2472      ol("source.</b>");
2473      ol("(note: the string <tt>\"WHERE\"</tt> does <b>not</b> have to be");
2474      ol("specified in the clause. It is added automatically by this method).");
2475      ol();
2476      ol("@return the number of rows deleted by the database");
2477      ol("*/"); 
2478      o("public static int deleteWhere(final Connection con, final String where) throws SQLException");
2479      ol("  {");
2480      ol("  __deletewhere_called++;");
2481      ol("  Argcheck.notnull(where, \"the where parameter was null (and should not be null)\");");
2482      ol("  final String stmt_string = \"DELETE from " + table.getName() + " WHERE \" + where ;");
2483      ol("  Statement stmt = con.createStatement();");
2484      ol("  log.bug(\"Query to run: \", stmt_string);");
2485      ol("  final int result = stmt.executeUpdate(stmt_string);");
2486      ol("  return result;");
2487      ol("}");
2488      }
2489    
2490    final void mgrWriteMethodDeleteUsing() throws SQLException
2491      { 
2492      ol();
2493      ol("/** ");
2494      ol("Returns the rows returned by querying the table with the contents of");
2495      ol("the specified instance of <tt>alltypes</tt> or <tt>null</tt> if no");
2496      ol("rows were found. As many fields in <tt>alltypes</tt> can be set as");
2497      ol("needed and the values of all set fields (including fields explicitly");
2498      ol("set to <tt>null</tt>) are then used to perform the query. <p>Note,");
2499      ol("however that this method does use any primary key(s). If the ");
2500      ol("primary keys are known then one should use the {@link");
2501      ol("#deleteByKey deleteByKey} method to delete the data instead.");
2502      ol("<p>");
2503      ol("This method is often convenient/safer than the {@link #deleteWhere");
2504      ol("deleteWhere} method (because the <tt>deleteWhere</tt> method takes");
2505      ol("an arbitrary query string which has to be properly escaped by the user).");
2506      ol();
2507      ol("<p>Essentially, this method is a more convenient way to use a");
2508      ol("PreparedStatement. Internally, a prepared statement is created and");
2509      ol("it's parameters are set to fields that are set in this object).");
2510      ol("Using PreparedStatements directly is also perfectly fine. For");
2511      ol("example, the following are equivalent. ");
2512      ol("<p> Using a PreparedStatement:");
2513      ol("<blockquote><pre>");
2514      ol("String foo = \"delete from table_foo where x = ? and y = ?\";");
2515      ol("PreparedStatement ps = con.prepareStatement(foo);");
2516      ol("ps.setString(1, \"somevalue\");");
2517      ol("ps.setString(2, \"othervalue\");");
2518      ol("int rows_deleted = ps.executeUpdate();");
2519      ol("</pre> </blockquote>  ");
2520      ol("");
2521      ol("Using this method:");
2522      ol("<blockquote><pre>");
2523      ol("table_foo proto = new table_foo();");
2524      ol("proto.set_x(\"somevalue\"); //compile time safety");
2525      ol("proto.set_y(\"othervalue\");  //compile time safety");
2526      ol("int rows_deleted = table_fooMgr.<font color=blue>deleteUsing</font>(proto);");
2527      ol("</pre></blockquote>");
2528      ol("@return   the number of rows deleted");
2529      ol("*/");
2530      o("public static int deleteUsing(final Connection con, final ");
2531      o(beanClassName);
2532      ol(" bean) throws ValidateException, SQLException");
2533      ol("  {");
2534      ol("  __deleteusing_called++;");
2535      ol();
2536      ol("  Argcheck.notnull(bean, \"the bean parameter was null (and should not be null)\");");
2537      ol("  if (! bean.isModified()) { ");
2538      ol("    throw new ValidateException(\"bean=\" + bean + \" not modified, ignoring query\");");
2539      ol("    }");
2540      ol();
2541      ol("  final StringBuilder buf = new StringBuilder(512);");
2542      o ("  buf.append(\"DELETE  from ");
2543      o (table.getName()); 
2544      ol(" WHERE \");");
2545      ol();
2546      ol("  int count = 0;");
2547      List cols = table.getColumnList();
2548      List relevant_cols = new ArrayList();
2549      for (int n = 0; n < cols.size(); n++) 
2550        {
2551        ColumnData cd = (ColumnData) cols.get(n); 
2552        //columns that are not PK only 
2553        if (cd.isPK()) 
2554          continue;
2555              
2556        o ("  if (bean.");
2557        o (wrangler.getIsModifiedName(cd));
2558        ol("()) { ");
2559            
2560        //for the later call to utilFillPStmtFromList_IfModified    
2561        relevant_cols.add(cd);  
2562    
2563        if (! cd.usesPrimitiveJavaType()) {
2564          o ("    if (bean."); o(wrangler.getGetName(cd)); ol("() == null) {");
2565          o ("      buf.append(\""); o(cd.getName()); ol(" is NULL and \");");
2566          ol("      }");
2567          ol("    else{");
2568          o ("      buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
2569          ol("      count++;");
2570          ol("      }");
2571          }
2572        else{
2573          o ("    buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
2574          ol("    count++;");
2575          }
2576        ol("    }");
2577        } //for
2578    
2579      if (relevant_cols.size() == 0) {
2580        ol("  throw new RuntimeException(\"This table contains to non-primary keys. Use the deleteByKey method instead.\");");
2581        ol("  }");
2582        return;
2583        }
2584        
2585      //get rid of last "and "
2586      ol("  buf.setLength(buf.length() - 4);"); 
2587      ol("  if (count == 0) {");
2588      ol("    throw new ValidateException(\"No non-PrimaryKey column was modified/set in this bean. You must set at least one such column. To delete by the Primary key, use the deleteByKey method instead.\");");
2589      ol("    }");
2590      ol("  final String getUsingPKStmt = buf.toString();");
2591      ol("  PreparedStatement ps = prepareStatement(con, getUsingPKStmt);");
2592    
2593      utilFillPStmtFromList_IfModified_Object(relevant_cols, "\t");
2594    
2595      ol("  log.bug(\"Query to run: \", ps);");
2596      ol("  List list = new ArrayList();");
2597      ol("  int result = ps.executeUpdate();");
2598    //  ol("  ps.clearParameters();");
2599      ol("  return result;"); 
2600      ol("  }");
2601      } //~write deleteusing
2602    
2603    
2604    final void mgrWriteMethodCount()
2605      {
2606      ol();
2607        ol("private final static String countStmt = \"SELECT count(*) from " + table.getName() + "\";");
2608      ol("/**");
2609      ol("Returns the count of all rows in the table. <p><b>Note</b>: This may");
2610      ol("be an expensive operation in MVCC databases like PostgresQL, Oracle and");
2611      ol("others, where an entire non-optimized table scan <i>may</i> be");
2612      ol("required -- hence speed will typically be O(n). However, on Postgres (for");
2613      ol("example), this is still very fast for small values of n (on a");
2614      ol("mid-level test machine) as of 2004, counting 4k records was about");
2615      ol("15 milli-seconds(ms); this scaled almost linearly, so count(*) for 16k records was");
2616      ol("about 70 ms, 65k records was about 370 ms, 524k records was about");
2617      ol("2000 ms and 1 million records was about 4000 ms. Results will vary");
2618      ol("on your machine and database but the general O(n) principle will");
2619      ol("remain the same.");
2620      ol("*/");
2621      ol("public static int count(final Connection con) throws SQLException");
2622      ol("  {");
2623      ol("  __count_called++;");
2624      ol("    int count = -1;");
2625      ol("  final Statement stmt = con.createStatement();");
2626        ol("    final ResultSet rs = stmt.executeQuery(countStmt);");
2627        ol("    if (rs.next())");
2628        ol("      {");
2629        ol("        count = rs.getInt(1);");
2630        ol("      }");
2631      ol("  else { //rs returned no count, which should never happen");
2632      ol("    throw new DBOException(\"The COUNT query [\" + countStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2633      ol("    }");
2634      ol("  stmt.close();");
2635      ol("  return count;");
2636      ol("  }");
2637        } //~write count
2638      
2639    final void mgrWriteMethodCountWhere()
2640      {
2641      ol();
2642      ol("/**");
2643      ol("Returns the count of rows in the table using the specified <tt>where</tt> clause.");
2644      ol("(note: the string <tt>\"WHERE\"</tt> does <b>not</b> have to be");
2645      ol("specified in the clause. It is added automatically by this method).");
2646      ol();
2647      ol("@throws   IllegalArgumentException  if the where paramater was null");
2648      ol("*/");
2649      ol("public static int countWhere(final Connection con, final String where) throws SQLException");
2650        ol("  {");
2651      ol("  __countwhere_called++;");
2652        ol("  Argcheck.notnull(where, \"the where parameter was null\");");
2653      ol("    int count = -1;");                              
2654        ol("  final String countWhereStmt = \"SELECT count(*) from " + table.getName() + " WHERE \" + where;");
2655      ol("  Statement stmt = con.createStatement();");
2656      //the mysql driver does not print stmt.toString() properly so we need
2657      //to also log the statement string
2658      ol("  log.bug(\"Query to run: \", stmt, \" \", countWhereStmt);");
2659        ol("    ResultSet rs = stmt.executeQuery(countWhereStmt);");
2660        ol("    if (rs.next())");
2661        ol("      {");
2662        ol("        count = rs.getInt(1);");
2663        ol("      }");
2664      ol("  else { //rs returned no count, which should never happen");
2665      ol("    throw new DBOException(\"The COUNT query [\" + countWhereStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2666      ol("    }");
2667      ol("  stmt.close();");
2668      ol("  return count;");
2669      ol("  }");
2670      } //~write countwhere
2671      
2672    final void mgrWriteMethodCountUsing() throws SQLException
2673      {
2674      ol();
2675      ol("/** ");
2676      ol("Returns the rows count by querying the table with the contents of the");
2677      o ("specified instance of <tt>");
2678      o (beanClassName);
2679      ol("</tt> As many fields in <tt>alltypes</tt> can be set as needed and the");
2680      ol("values of all set fields (including fields explicitly set to");
2681      ol("<tt>null</tt>) are then used to perform the query. If the primary");
2682      ol("key(s) are known then one can also use the {@link #exists} method to");
2683      ol("see if that row exists in the database.");
2684      ol("<p>");
2685      ol("This method is often convenient/safer than the {@link #countWhere");
2686      ol("countWhere} method (because the <tt>countWhere</tt> method takes an");
2687      ol("arbitrary query string which has to be properly escaped by the");
2688      ol("user). ");
2689      ol("<p>Essentially, this method is a more convenient way to use a");
2690      ol("PreparedStatement (with parameters set to fields that are set in");
2691      ol("this object). Using PreparedStatements directly is also perfectly");
2692      ol("fine. For example, the following two are equivalent. <p>");
2693      ol("Using a PreparedStatement:");
2694      ol("<blockquote><pre>");
2695      ol("String foo = \"select <i>count(*)</i> from table_foo where x = ? and y = ?\";");
2696      ol("PreparedStatement ps = con.prepareStatement(foo);");
2697      ol("ps.setString(1, \"somevalue\");");
2698      ol("ps.setString(2, \"othervalue\");");
2699      ol("ResultSet rs  = ps.executeUpdate();");
2700      ol("rs.next();");
2701      ol("int count = rs.getInt(1);");
2702      ol("</pre> </blockquote>");
2703      ol("");
2704      ol("Using this method:");
2705      ol("<blockquote><pre>");
2706      ol("table_foo proto = new table_foo();");
2707      ol("proto.set_x(\"somevalue\"); //compile time safety");
2708      ol("proto.set_y(\"othervalue\");  //compile time safety");
2709      ol("int count = table_fooMgr.<font color=blue>countUsing</font>(proto);");
2710      ol("</pre> </blockquote>"); 
2711      ol("*/");
2712      o("public static int countUsing(final Connection con, final ");
2713      o(beanClassName);
2714      ol(" bean) throws ValidateException, SQLException");
2715      ol("  {");
2716      ol("  __countusing_called++;");
2717      o ("  Argcheck.notnull(bean, \"the bean parameter was null (and should not be null)\");");
2718      ol("  if (! bean.isModified()) { ");
2719      ol("    throw new ValidateException(\"bean=\" + bean + \" not modified, ignoring query\");");
2720      ol("    }");
2721      ol();
2722      ol("  int count = 0;");
2723      ol("  final StringBuilder buf = new StringBuilder(512);");
2724      o ("  buf.append(\"SELECT count(*)  from " );
2725      o (table.getName());
2726      ol(" WHERE \");");
2727    
2728      List cols = table.getColumnList();
2729      List relevant_cols = new ArrayList();
2730      for (int n = 0; n < cols.size(); n++) 
2731        {
2732        ColumnData cd = (ColumnData) cols.get(n); 
2733        
2734        //for the later call to utilFillPStmtFromList_IfModified    
2735        relevant_cols.add(cd);  
2736          
2737        o ("  if (bean.");
2738        o (wrangler.getIsModifiedName(cd));
2739        ol("()) { ");
2740        if (! cd.usesPrimitiveJavaType()) {
2741          o ("    if (bean."); o(wrangler.getGetName(cd)); ol("() == null) {");
2742          o ("      buf.append(\""); o(cd.getName()); ol(" is NULL and \");");
2743          ol("      }");
2744          ol("    else{");
2745          o ("      buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
2746          ol("      count++;");
2747          ol("      }");
2748          }
2749        else{
2750          o ("    buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
2751          ol("    count++;");
2752          }
2753        ol("    }");
2754        }   
2755      ol();
2756      
2757      //get rid of last "and "
2758      ol("  buf.setLength(buf.length() - 4);"); 
2759      ol();
2760      ol("  final String countUsingStmt = buf.toString();");
2761      ol("  PreparedStatement ps = prepareStatement(con, countUsingStmt);");
2762    
2763      utilFillPStmtFromList_IfModified_Object(relevant_cols, "\t");
2764    
2765      ol("  log.bug(\"Query to run: \", ps);");
2766      ol("  ResultSet rs = ps.executeQuery();");
2767      ol("  if (! rs.next()) {");
2768      ol("    throw new DBOException(\"The COUNT query [\" + countUsingStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2769      ol("    }");
2770      ol("  int rows = rs.getInt(1);");
2771      ol("  rs.close();");
2772    //  ol("  ps.clearParameters();");
2773      ol("  return rows;"); 
2774      ol("  }");
2775      } //~write getusing
2776      
2777      
2778    final void mgrWriteMethodExists() throws SQLException
2779      { 
2780      if (pklist.size() == 0) {
2781        ol("/* exists() not implemented since this table does not have any primary keys defined */");
2782        return;
2783        }
2784        
2785      ol();
2786      o("static private final String existsStmt = \"SELECT count(*) from "); 
2787      o(table.getName());
2788      o(" WHERE ");
2789      o(Table.getPreparedStmtPlaceholders(pklist));
2790      ol("\";");
2791      
2792      ol("/**");
2793      ol("Returns <tt>true</tt> if a row with the specified primary keys exists, <tt>false</tt> otherwise.");
2794      ol("<p>This method uses a prepared statement and is safe from SQL injection attacks");
2795      ol("*/");
2796      o("public static boolean exists(final Connection con, "); 
2797      //example: int col_a, String col_b ....
2798      o(pkFormalParamString);  
2799      ol(") throws SQLException");
2800      ol("  {");
2801      ol("  __exists_called++;");
2802      ol("  PreparedStatement ps = prepareStatement(con, existsStmt);");
2803      for (int n = 0; n < pklist.size(); n++)
2804        {
2805        ColumnData cd = (ColumnData) pklist.get(n); 
2806        String varname = cd.getName(); //example: col_a
2807        o("\t");
2808        String pos = String.valueOf((n+1));
2809        ol(cd.getPreparedStmtSetMethod("ps.", pos, varname));
2810        }
2811      ol("  log.bug(\"Query to run: \", ps);");
2812      ol("  ResultSet rs = ps.executeQuery();");
2813      ol("  int count = -1;");
2814        ol("    if (rs.next())");
2815        ol("      {");
2816        ol("        count = rs.getInt(1);");
2817        ol("      }");
2818      ol("  else { //rs returned no count, which should never happen");
2819      ol("    throw new DBOException(\"The COUNT query [\" + existsStmt + \"] returned no rows. [Should have returned 1 row exactly] \");");
2820      ol("    }");
2821      ol("  rs.close();");
2822    //  ol("  ps.clearParameters();");
2823      ol("  return (count > 0); //exists if count > 0");
2824      ol("  }");
2825      }
2826      
2827    final void mgrWriteMethodExistsUsing() throws SQLException
2828      { 
2829      ol("/**");
2830      o ("A thin wrapper around {@link getUsing(Connection,"); o(beanClassName);
2831      ol(") getUsing}");
2832      ol("that returns <tt>false</tt> if no rows are returned, <tt>true</tt> otherwise.");
2833      ol("*/");
2834      o("public static boolean existsUsing(final Connection con, final ");
2835      o(beanClassName);
2836      ol(" bean) throws ValidateException, SQLException");
2837      ol("  {");
2838      ol("  final List list = getUsing(con, bean, null);"); 
2839      ol("  return (list.size() > 0);");
2840      ol("  }");
2841      }
2842      
2843    final void mgrWriteMethodPrepareStatement()
2844      {
2845      ol();
2846      ol("/**");
2847      ol("Returns a prepared statement given it's variable name.");
2848      ol();
2849      ol("Essentially speeds up the creation of prepared statements perhaps");
2850      ol("using a per connection cache -- which makes sense for pooled");
2851      ol("connections since they are not closed but returned to the pool");
2852      ol("and hence don't need to \"prepare\" statements every time (the");
2853      ol("prepareStatement call is seperate from actually filling in the");
2854      ol("placeholders in a already created prepared statement -- which");
2855      ol("does need to be done every time). <p>");
2856      ol("Prepared statements are unique per connection, so multiple threads");
2857      ol("using different connections won't stomp over each other's prepared");
2858      ol("statements. Multiple threads using the SAME connection will cause");
2859      ol("bizarre errors but multiple threads won't get the same connection");
2860      ol("from the connection manager -- ever :-), so that should never happen.");
2861      ol("*/");
2862      o("private static final PreparedStatement prepareStatement (");
2863      ol(" final Connection con, final String sql) throws SQLException");
2864      ol("  {");
2865      ol("  if (! (con instanceof fc.jdbc.PooledConnection) ) { ");
2866      ol("    return con.prepareStatement(sql);");
2867      ol("    }");
2868      ol("  final PooledConnection pc = (PooledConnection) con;");
2869      ol("  return pc.getCachedPreparedStatement(sql);");
2870      ol("  }");
2871      }
2872    
2873    final void mgrWriteCheckDiscarded()
2874      {
2875      ol();
2876      ol("private static final void checkDiscarded(final DBO bean) throws DBOException");
2877      ol("  {");
2878      ol("  if (bean.isDiscarded()) {");
2879      ol("    throw new DBOException(\"===== Attempt to save a discarded object === \" + bean);");  
2880      ol("    }");
2881      ol("  }");
2882      }
2883      
2884    final void mgrWriteMethodStats()
2885      { 
2886      ol();
2887      ol("private static final java.util.Date __loadDate = new java.util.Date();");
2888      ol("/** Returns usage statistics for this class */");
2889      ol("public static String stats() ");
2890      ol("  {");
2891      ol("  //locally created _numberFormat for thread safety");
2892      ol("  final java.text.NumberFormat _numberFormat = java.text.NumberFormat.getInstance();");
2893      ol("  final String nl = fc.io.IOUtil.LINE_SEP;");
2894      ol("  StringBuffer buf = new StringBuffer(256);");
2895      o ("  buf.append(\"Class Name: [");
2896      o (mgrClassName);
2897      ol("]; Class loaded on: \");"); 
2898      ol("  buf.append(__loadDate);");
2899      ol("  buf.append(nl);");
2900      o ("  buf.append(\"---- Start Usage Statistics ----\")"); ol(".append(nl);");
2901      ol();
2902      ol("  ByteArrayOutputStream out = new ByteArrayOutputStream(512);");
2903      ol("  TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();");
2904      ol("  config.setPrintBorders(false);");
2905      ol("  config.setCellSpacing(1);");
2906      ol("  config.setCellPadding(0);");
2907      ol("  config.setAutoFit(true);");
2908      ol("  TablePrinter p = new TablePrinter(2, new PrintStream(out), config);");
2909      ol("  p.startTable();");
2910      ol();
2911      ol("  p.startRow();");
2912      ol("  p.printCell(\"Method\");");
2913      ol("  p.printCell(\"# times called\");");
2914      ol("  p.endRow();");
2915      ol();
2916      ol("  p.startRow();");
2917      ol("  p.printCell(\"getAll()\");");
2918      ol("  p.printCell(_numberFormat.format(__getall_called));");
2919      ol("  p.endRow();");
2920      ol();
2921      ol("  p.startRow();");
2922      ol("  p.printCell(\"getLimited()\");");
2923      ol("  p.printCell(_numberFormat.format(__getlimited_called));");
2924      ol("  p.endRow();");
2925      ol();
2926      ol("  p.startRow();");
2927      ol("  p.printCell(\"getByKey()\");");
2928      ol("  p.printCell(_numberFormat.format(__getbykey_called));");
2929      ol("  p.endRow();");
2930      ol();
2931      ol("  p.startRow();");
2932      ol("  p.printCell(\"getWhere()\");");
2933      ol("  p.printCell(_numberFormat.format(__getwhere_called));");
2934      ol("  p.endRow();");
2935      ol();
2936      ol("  p.startRow();");
2937      ol("  p.printCell(\"getUsing()\");");
2938      ol("  p.printCell(_numberFormat.format(__getusing_called));");
2939      ol("  p.endRow();");
2940      ol();
2941      ol("  p.startRow();");
2942      ol("  p.printCell(\"getUsing(prepared_stmt)\");");
2943      ol("  p.printCell(_numberFormat.format(__getusing_ps_called));");
2944      ol("  p.endRow();");
2945      ol();
2946      ol("  p.startRow();");
2947      ol("  p.printCell(\"getFromRS()\");");
2948      ol("  p.printCell(_numberFormat.format(__getfromrs_called));");
2949      ol("  p.endRow();");
2950      ol();
2951      ol("  p.startRow();");
2952      ol("  p.printCell(\"save()\");");
2953      ol("  p.printCell(_numberFormat.format(__save_called));");
2954      ol("  p.endRow();");
2955      ol();
2956      ol("  p.startRow();");
2957      ol("  p.printCell(\"delete()\");");
2958      ol("  p.printCell(_numberFormat.format(__delete_called));");
2959      ol("  p.endRow();");
2960      ol();
2961      ol("  p.startRow();");
2962      ol("  p.printCell(\"deleteByKey()\");");
2963      ol("  p.printCell(_numberFormat.format(__deletebykey_called));");
2964      ol("  p.endRow();");
2965      ol();
2966      ol("  p.startRow();");
2967      ol("  p.printCell(\"deleteWhere()\");");
2968      ol("  p.printCell(_numberFormat.format(__deletewhere_called));");
2969      ol("  p.endRow();");
2970      ol();
2971      ol("  p.startRow();");
2972      ol("  p.printCell(\"deleteUsing()\");");
2973      ol("  p.printCell(_numberFormat.format(__deleteusing_called));");
2974      ol("  p.endRow();");
2975      ol();
2976      ol("  p.startRow();");
2977      ol("  p.printCell(\"count()\");");
2978      ol("  p.printCell(_numberFormat.format(__count_called));");
2979      ol("  p.endRow();");
2980      ol();
2981      ol("  p.startRow();");
2982      ol("  p.printCell(\"countWhere()\");");
2983      ol("  p.printCell(_numberFormat.format(__countwhere_called));");
2984      ol("  p.endRow();");
2985      ol();
2986      ol("  p.startRow();");
2987      ol("  p.printCell(\"countUsing()\");");
2988      ol("  p.printCell(_numberFormat.format(__countusing_called));");
2989      ol("  p.endRow();");
2990      ol();
2991      ol("  p.startRow();");
2992      ol("  p.printCell(\"exists()\");");
2993      ol("  p.printCell(_numberFormat.format(__exists_called));");
2994      ol("  p.endRow();");
2995      ol();
2996      ol("  p.endTable();");
2997      ol("  buf.append(out.toString());");
2998      ol("  return buf.toString();");
2999      ol("  }");
3000      }
3001      
3002    final void mgrWriteMethodToString()
3003      { 
3004      ol();
3005      ol("public String toString() ");
3006      ol("  {");  
3007      ol("  return getClass().getName() + \" [call stats() for more info]\";");
3008      ol("  }");
3009      }
3010    
3011    void mgrWriteValidators() throws SQLException
3012      {
3013      ol();
3014      ol("// ================ Validation  ==================== ");
3015      ol();
3016      ol("/** ");
3017      ol("Creates and attaches validators for all the fields in the");
3018      ol("specified {@link fc.web.forms.Form}. These fields should");
3019      o("<i>have the same name</i> in the form as in {@link "); o(beanClassName);
3020      ol("}. If this is not the case, then the then the differences can be specifed");
3021      ol("as follows. <p>");
3022      ol("<dl>");
3023      ol("<dt>with a prefix</dt>");
3024      o ("  <dd><tt>(prefix + ");o(beanClassName);ol(" column)</tt> should equal <tt>form fieldname</tt></dd>");
3025      ol("<dt>with a suffix</dt> ");
3026      o ("  <dd><tt>(");o(beanClassName);ol(" column + suffix)</tt> should equal <tt>form fieldname</tt></dd>");
3027      ol("<dt>with both a prefix/suffix</dt> ");
3028      o ("  <dd><tt>(prefix + ");o(beanClassName);ol(" + suffix)</tt> should equal <tt>form fieldname</tt></dd>");
3029      ol("<dt>with a arbitrary map</dt> ");
3030      o ("  <dd>[key] <tt>");o(beanClassName);ol(" column</tt> -> [value] <tt>form fieldname</tt>");
3031      ol("  <u>If a map is specified, then the prefix/suffix are not used.</u>");
3032      ol("  </dd>");
3033      ol("</dl>");
3034      ol("<p>These validators are for database constraints such as <i>nullability</i> & <i>column length</i>.");
3035      ol("These validators save a lot of grunt-work in adding such schema");
3036      ol("constraints to the front-end {@link fc.web.forms.Form}. <p><b>However, <i>business and");
3037      ol("other validation constraints</i> still need to be manually added to");
3038      ol("the application code/front-end forms as/when needed</b>.");
3039      ol("<p>");
3040      ol("");
3041      ol("The following table shows the kind of validators added by this method");
3042      ol("<table border=1 width=90%>");
3043      ol("<tr bgcolor='#CCCCCC'>");
3044      ol("  <td>Database SQL Type</td>");
3045      ol("  <td><b>Nullable</b>validator</td>");
3046      ol("  <td><b>Length</b> validator</td>");
3047      ol("  <td><b>Digits only</b> input validator ({@link VText#allowIntegersOnly})</td>");  
3048      ol("</tr>");
3049      ol("  <tr>");
3050      ol("    <td><tt>CHAR</tt>, <tt>VARCHAR</tt></td>");
3051      ol("    <td>Yes (maximum length constraint).<br><font size='-1' color=red>This");
3052      ol("    only applies to form fields that are subclasses of {@link ");
3053      ol("    fc.web.forms.MaxSizable} </font></td>");
3054      ol("    <td>-NO-</td>");
3055      ol("  </tr>");
3056      ol("  <tr>");
3057      ol("    <td><tt>TINYINT, MEDIUMINT, INT, BIGINT (integral types)</tt></td>");
3058      ol("    <td>Yes</td>");
3059      ol("    <td>-NO-</td>");
3060      ol("    <td>Yes to integer columns displayed using form fields that are subclasses of {@link fc.web.forms.AbstractText}<br> Note: <b>not</b> added non-<i>integral</i> number types such as <tt>FLOAT, REAL, DOUBLE, NUMERIC/DECIMAL</tt></td>");
3061      ol("  </tr>");
3062      ol("  <tr>");
3063      ol("    <td>All other SQL types</td>");
3064      ol("    <td>Yes</td>");
3065      ol("    <td>-NO-</td>");
3066      ol("  </tr>");
3067      ol("</table>");
3068      ol("<p>Automatic validators are very useful but can be very tricky to understand. It is");
3069      ol("suggested to invoke this method, print the form using it's <tt>toString</tt>");
3070      ol("method and then examine the output to see what validators were added If those");
3071      ol("automatic validators are too little, too many or too hard to understand, <u>then");
3072      ol("simply enoough, do NOT invoke this method and simply add validators by");
3073      ol("hand</u>. In particular, do <i>not</i> add automatic validators for");
3074      ol("<b>tables</b> in which a row is optional but <i>if</i> some column is filled in");
3075      ol("the front end form, <i>then</i> all columns must be filled.");
3076      ol();
3077      ol("@param  form  the form containing fields (some or all) representing");
3078      ol("        this and possible other tables. These field");
3079      ol("        objects must have been added to the form prior");
3080      ol("        to calling this method");
3081      ol("@param  prefix  an optional (null allowed) prefix to this table's column name with which the");
3082      ol("        corresponding column was added to the form.");
3083      ol("        A <tt>*</tt> specifies all possible prefixes");
3084      ol("@param  suffix  an optional suffix (null allowed) to this table's column name with which the ");
3085      ol("        corresponding column was added to the form.");
3086      ol("        A <tt>*</tt> specifies all possible suffixes");
3087      ol("@param  map   an optional map (null allowed) that maps this table's column name with which the ");
3088      ol("        corresponding column was added to the form. ");
3089      ol("        [key] <tt>table's column_name</tt> -> [value] <tt>form's fieldname</tt>");
3090      ol("*/"); 
3091      ol("public static void addValidators(final fc.web.forms.Form form, final String prefix, final String suffix, final Map map) ");
3092      ol("  {");
3093      ol("  addValidators(form, prefix, suffix, map, false);");
3094      ol("  }");
3095      ol();
3096      
3097      ol("private static void addValidators(fc.web.forms.Form form, String prefix, String suffix, final Map map, final boolean onlyOnFilled) ");
3098      ol("  {");
3099      ol("  List list = null;");  
3100      ol("  Argcheck.notnull(form, \"How can I add validators to the form when the form parameter was null ?\");");
3101      ol("  Field field = null;");
3102      ol("  FieldValidator fv = null;");
3103      ol("  String colname_in_form = null;");
3104      ol("  //fields can be null if they are not being used in the html form");
3105      List cols = table.getColumnList();
3106      for (int n = 0; n < cols.size(); n++)
3107        {
3108        ColumnData cd = (ColumnData) cols.get(n);
3109        String colname = cd.getName();
3110        String sqltypename = cd.getSQLTypeName().intern();
3111        ol();
3112        o ("\t");ol(getBeanComment(cd));
3113        o ("  list = getFieldFromForm(form, \"");
3114        o (colname); 
3115        o ("\", prefix, suffix, map, ");
3116      
3117        //false=no warn message if this field not in form
3118        if (cd.isAutoIncrement()) 
3119          o("false"); 
3120        else
3121          o("true");
3122          
3123        ol(");");
3124        ol("  if (list.size() > 0) ");
3125        ol("    { //add applicable automatic validators, empty if n/a");
3126        ol("    for (int n = 0; n < list.size(); n++)");
3127        ol("      {");
3128        ol("      field = (Field) list.get(n);");
3129    
3130        //nullable
3131        if (! cd.isNullable()) 
3132          {
3133          if (cd.hasDefaultValue()) {
3134            o ("      /* field is non-nullable but has a default value [");
3135            o (cd.getDefaultValue()); 
3136            ol("], skipping non-nullability validation */");
3137            }
3138          else{
3139            ol("      if (field instanceof Choice) {");
3140            ol("        //choice fields are ignored because they can");
3141            ol("        //mean false even when NOT selected/filled");
3142            ol("        continue;");
3143            ol("        }");
3144            ol("      /* field is non-nullable */");
3145            o ("      fv = new VFilled(field, \"");
3146            o(validateNull_ErrorMsg);
3147            ol("\");");         
3148            }
3149          }
3150        else{
3151          ol("      /* field is nullable, skipping non-nullability validation */");
3152          }
3153    
3154        //numbers only  
3155        if (sqltypename.toLowerCase().indexOf("int") >= 0)
3156          {
3157          ol();
3158          ol("      /* database type for this field is integral */");
3159          ol("      if (field instanceof AbstractText) {");
3160          o ("        fv = new VText((AbstractText)field, \"");
3161          o(validateIntegerOnly_ErrorMsg);
3162          ol("\")");
3163          ol("          .allowIntegersOnly();");
3164          ol("        }");
3165          }
3166        
3167        //length
3168        if ((sqltypename == "CHAR" || sqltypename  == "VARCHAR"))
3169          /*
3170          colsize IS NOT accurate/reliable for non text types.
3171          (therefore we only set maxsize for char/varchar)
3172          
3173          text(longvarchar) size is not knowable with postgres and postgres
3174          returns -1 for LONGVARCHAR Except now we have another bug, the
3175          current driver returns VARCHAR even for text columns (instead of
3176          LONGVARCHAR). What the fuck ? They are fucking up the best free
3177          database on the planet with a shitty jdbc driver. god, it's almost
3178          5am and i'm tired. p.s: postgres jdbc driver + support is still
3179          waaay better than all other competing db's though.
3180          */
3181          {
3182          //bug workaround
3183          int colsize = cd.getSize();
3184          if (colsize < 0)
3185            colsize = Integer.MAX_VALUE;
3186          //end workaround
3187    
3188          ol("      if (! (field instanceof MaxSizable)) {");
3189          o ("        log.warn(\"Skipping maximum length validator for field '\" + field.getName() + \"'; [database type='");o(sqltypename);ol("', field.type='\" +  field.getType() + \"' is not MaxSizable]\"); ");
3190          ol("        }");
3191          ol("      else{");
3192          o ("        VText vt = new VText((MaxSizable) field, \"");
3193          o(validateText_ErrorMsg_MaxSize);
3194          ol("\");");         
3195          o ("        vt.setMaxSize(");
3196          o (String.valueOf(colsize));
3197          ol(");");
3198          ol("        }");
3199          }
3200        ol("      }");  //generated for
3201        ol("    }");  
3202        }
3203      ol("  }");
3204      ol();
3205    
3206      ol("/** ");
3207      ol("Convenience method that calls {@link #addValidators(Form, String, String, Map)} with a ");
3208      ol("<tt>null</tt> prefix/suffix and the specified map");
3209      ol("*/");
3210      ol("public static void addValidators(fc.web.forms.Form form, Map map) ");
3211      ol("  {");
3212      ol("  addValidators(form, null, null, map);");
3213      ol("  }");
3214      ol();
3215    
3216      ol("/** ");
3217      ol("Convenience method that calls {@link #addValidators(Form, String, String, Map)} with a ");
3218      ol("<tt>null</tt> prefix/suffix/map");
3219      ol("*/");
3220      ol("public static void addValidators(fc.web.forms.Form form) ");
3221      ol("  {");
3222      ol("  addValidators(form, null, null, null);");
3223      ol("  }");
3224      ol();
3225    
3226      ol("/** ");
3227      ol("Convenience method that calls {@link #addValidators(Form, String, String, map)} with the ");
3228      ol("specified prefix and a <tt>null</tt> suffix/map");
3229      ol("*/");
3230      ol("public static void addValidators(fc.web.forms.Form form, String prefix) ");
3231      ol("  {");
3232      ol("  addValidators(form, prefix, null, null);");
3233      ol("  }");
3234      ol();
3235    
3236      ol("/** ");
3237      ol("Validates a form field <i>if</i> it is filled by the user. Leaves empty fields alone.");
3238      ol("This is very useful for fields that are optional but must have the correct value when");
3239      ol("filled by the user");
3240      ol("*/");
3241      ol("public static void addIfFilledValidators(Form form, String prefix) ");
3242      ol("  {");
3243      ol("  addValidators(form, prefix, null, null, true);");
3244      ol("  }");
3245      ol();
3246      
3247      ol("/** implementation helper method -- not for public use */");
3248      ol("static List getFieldFromForm(Form form, String colname, String prefix, String suffix, Map map, boolean warn)");
3249      ol("  {");
3250      ol("  Field field = null;");
3251      ol("  List list = Form.empty_list;");
3252      ol("  boolean getwhere = false;");
3253      ol("  getwhere = false;");
3254      ol("  String colname_in_form = colname;");
3255      ol();
3256      ol("  if (map != null) {");
3257      ol("    String str = (String) map.get(colname);");
3258      ol("    if (str != null) {");
3259      ol("      prefix = null;  /*ignored when there is a mapping*/");
3260      ol("      suffix = null;  /*ignored when there is a mapping*/");
3261      ol("      colname_in_form = str; /* else if not in map, colname remains as-is*/");
3262      ol("      }");
3263      ol("    }");
3264      ol();
3265      ol("  if (prefix != null) ");
3266      ol("    { ");
3267      ol("    if (prefix.equals(\"*\")) { ");
3268      ol("      getwhere = true;");
3269      ol("      }");
3270      ol("    else{");
3271      ol("      colname_in_form = prefix + colname_in_form;");
3272      ol("      }");
3273      ol("    }");
3274      ol();
3275      ol("  if (suffix != null) ");
3276      ol("    {");
3277      ol("    if (suffix.equals(\"*\")) { ");
3278      ol("      getwhere = true;");
3279      ol("      }");
3280      ol("    else{");
3281      ol("      colname_in_form = colname_in_form + suffix;");
3282      ol("      }");
3283      ol("    }");
3284      ol();
3285      ol("  if (getwhere) { ");
3286      ol("    list = form.getContaining(colname_in_form);");
3287      ol("    if (list.size() == 0 && warn) warn(form, colname_in_form, suffix, prefix, map);");
3288      ol("    return list;");
3289      ol("    }");
3290      ol("  else{");
3291      ol("    //containsField() check prevents an un-necessary warning with form.get()");
3292      ol("    if (! form.containsField(colname_in_form)) {");
3293      ol("      if (warn) warn(form, colname_in_form, suffix, prefix, map);");
3294      ol("      return list;");
3295      ol("      }");
3296      ol("    field = form.get(colname_in_form);" );
3297      ol("    list = new ArrayList();");
3298      ol("    list.add(field);");
3299      ol("    }");
3300      ol("  return list;");
3301      ol("  }");
3302    
3303      ol();
3304      ol("private static final void warn(Form form, String name, String suffix, String prefix, Map map) {");
3305      ol("  log.warn(form.getName(),\": No automatic validators will be added for Field [\",name,\"]. This field does not exist in the front-end form. (this could be normal). suffix=[\"+suffix+\"] prefix=[\"+prefix+\"] map=\", map);");
3306      ol("  }");
3307      
3308      //ValidateBeforeSaveNew 
3309      ol();
3310      ol("/** ");
3311      ol("Validates the bean before saving it to the database. This");
3312      ol("method is called internally by the {@link save()} method");
3313      ol("before saving a new bean (i.e., inserting a new row) to the");
3314      ol("database.");
3315      ol("<p>");
3316      ol("The validation is somewhat basic and there can exist many");
3317      ol("constraints and conditions on the database that might results in a");
3318      ol("insert/update error anyway. But by doing some basic validation");
3319      ol("against some known constraints, we save a needless trip to the");
3320      ol("database.");
3321      ol("We check to see that: <ol>");
3322      ol("<li><i>non-nullable and non auto-increment</i> columns [and with no default");
3323      ol("column value in the database] are modified (via a set method) since these");
3324      ol("columns must have a explicitly set value.</li>");
3325      ol("<li>for non-nullable columns that hold non-primitive (i.e., Object)");
3326      ol("java types, the modified value for non-nullable columns is a");
3327      ol("non-null object. </li>");
3328      ol("</ol>");
3329      ol("*/");
3330      o ("protected static void validateBeforeSaveNew(");
3331      o (beanClassName);
3332      ol(" bean) throws ValidateException");
3333      ol("  {");
3334      ol("  boolean error = false;");
3335      ol("  final StringBuffer buf = new StringBuffer(\"The following validation errors were found\").append(IOUtil.LINE_SEP);");
3336      for (int n = 0; n < cols.size(); n++)
3337      {
3338      ColumnData cd = (ColumnData) cols.get(n);
3339      String colname = cd.getName();
3340      ol();
3341      if (cd.isNullable()) {
3342        o ("  //["); o (colname);
3343        ol("]=>nullable, no modification check necessary, skipping...");
3344        }
3345      else if ( ! cd.isNullable() && cd.hasDefaultValue()) {
3346        o ("  //["); o (colname);
3347        o ("]=>is not nullable but has a default column value [");
3348        o (cd.getDefaultValue()); 
3349        ol("], no modification check necessary, skipping...");
3350        }
3351      else if (cd.isAutoIncrement()) {
3352        o ("  //["); o (colname);
3353        ol("]=>auto-increment, no modification check necessary, skipping...");
3354        }   
3355      else { //not nullable and value required
3356        o(" if (! bean.");
3357        o(wrangler.getIsModifiedName(cd));
3358        ol("()) {");
3359        ol("    error = true;");
3360        o ("    buf.append(\"");
3361        o (colname);
3362        o (" was not set (this field is required in the database)\").append(\";current value=\"");
3363        o (").append(bean.");
3364        o (wrangler.getGetName(cd));
3365        ol("()).append(IOUtil.LINE_SEP);");
3366        ol("    }");
3367        
3368        if (! cd.usesPrimitiveJavaType())
3369          {
3370          ol("  else { //was modified but to null");
3371          o ("    if (bean.");
3372          o (wrangler.getGetName(cd));
3373          ol("() == null) {");
3374          ol("      error = true;");
3375          o ("      buf.append(\"");
3376          o (colname);
3377          o (" was set to null (but is non-nullable)\").append(\";current value=\"");
3378          o (").append(bean.");
3379          o (wrangler.getGetName(cd));
3380          ol("()).append(IOUtil.LINE_SEP);");
3381            ol("      }");
3382            ol("    }");
3383            }
3384          else{
3385            o ("  //");
3386          o (cd.getJavaTypeFromSQLType());
3387          ol(" is primitive, skipping null test");      
3388          }
3389        }
3390      } //for
3391    
3392      ol("  if (error) { ");
3393      ol("    throw new ValidateException(buf.toString());");
3394      ol("    }");
3395      ol("  }");
3396    
3397      //ValidateBeforeSaveUpdate
3398      ol();
3399      ol("/** ");
3400      ol("Validates the bean before saving it to the database. This method is");
3401      ol("called internally by the {@link save()} method before updating an");
3402      ol("existing bean (i.e., updating a row) in the database.");
3403      ol("<p>");
3404      ol("For <i>each modified column</i>, if that column is non-nullable in");
3405      ol("the database, then it must have a non-null value in the bean before");
3406      ol("it is saved. This check is only done for fields of");
3407      ol("<i>non</i>-primitive (Object) java types. [There is no way to ensure");
3408      ol("a non-null value for <i>primitive</i> types since all values");
3409      ol("[including 0] are non-null for those types].");
3410      ol("*/");
3411      o ("protected static void validateBeforeSaveUpdate(");
3412      o (beanClassName);
3413      ol(" bean) throws ValidateException");
3414      ol("  {");
3415      ol("  boolean error = false;");
3416      ol("  final StringBuffer buf = new StringBuffer(\"The following validation errors were found\").append(IOUtil.LINE_SEP);");
3417      ol();
3418    
3419      for (int n = 0; n < cols.size(); n++)
3420        {
3421        ColumnData cd = (ColumnData) cols.get(n);
3422        String colname = cd.getName();
3423    
3424        if (cd.isNullable()) {
3425          o ("  //["); o (colname);
3426          ol("]=>nullable, no modification check necessary, skipping...");
3427          }
3428        /* 
3429        dont check for existence of default column value here, if it's an
3430        update, prolly a good idea for the bean to have a updated value and not
3431        rely on column defaults 
3432        */
3433        else if (cd.isAutoIncrement()) {
3434          o ("  //["); o (colname);
3435          ol("]=>auto-increment, no modification check necessary, skipping...");
3436          }
3437        /*
3438        else if (cd.isPK()) { //must be able to find the row to update
3439          o(" if (! bean.");
3440          o(wrangler.getIsModifiedName(cd));
3441          ol("()) {");
3442          ol("    error = true;");
3443          o ("    buf.append(\"");
3444          o (colname);
3445          ol(" was not set (this field is a primary key and is needed to find the row to update.\");");
3446          ol("    }");  
3447          }
3448        */
3449        /* unlike validatebeforesave, we don't check to see if every 
3450        required column has been modified (an update allows updates
3451        whatever is modified, and leaves the rest (required or non required)
3452        alone
3453        */
3454        else{
3455          o(" if (bean.");
3456          o(wrangler.getIsModifiedName(cd));
3457          ol("()) {");    
3458          if (! cd.usesPrimitiveJavaType())
3459            {
3460            o ("    if (bean.");
3461            o (wrangler.getGetName(cd));
3462            ol("() == null) {");
3463            ol("      error = true;");
3464            o ("      buf.append(\"");
3465            o (colname);
3466            o (" was set to null (but is non-nullable)\").append(\";current value=\"");
3467            o (").append(bean.");
3468            o (wrangler.getGetName(cd));
3469            ol("()).append(IOUtil.LINE_SEP);");
3470            ol("      }");
3471            }
3472          else{
3473            o ("    //");
3474            o (cd.getJavaTypeFromSQLType());
3475            ol(" is primitive, skipping null test");      
3476            }
3477            ol("    }");
3478          }
3479        } //for
3480    
3481      ol("  if (error) { ");
3482      ol("    throw new ValidateException(buf.toString());");
3483      ol("    }");
3484      ol("  }");
3485    
3486      }//~mgrWriteValidators()
3487    
3488    void o(String str) {
3489      out.print(str);
3490      }
3491    
3492    void o(String str, String str2) {
3493      out.print(str);
3494      out.print(str2);
3495      }
3496    
3497    void ol(String str) {
3498      out.println(str); 
3499      }
3500    
3501    void ol() {
3502      out.println();
3503      }
3504    
3505    void utilFillPStmtFromList_IfModified (
3506      final List list, final String tabprefix) 
3507    throws SQLException
3508      {
3509      final String tabprefix2 = tabprefix + "\t";
3510      //final String tabprefix3 = tabprefix + "\t\t";
3511    
3512      o(tabprefix);
3513      ol("int pos = 0;");
3514    
3515      /* Example: for all columns, output:
3516      if (bean.isModified_col_a()) 
3517        {  
3518        pos++;  
3519        int col_a = bean.get_col_a();
3520        ps.set(pos, col_a);
3521        }             
3522      */
3523      for (int n = 0; n < list.size(); n++)
3524        {
3525        ColumnData cd = (ColumnData) list.get(n); 
3526        String colname = cd.getName();
3527    
3528        o(tabprefix);
3529        o("if (bean.");
3530        o(wrangler.getIsModifiedName(cd));
3531        ol("()) {");
3532    
3533        o(tabprefix2);
3534        ol("pos++;");
3535    
3536        o(tabprefix2);
3537        o(cd.getJavaTypeFromSQLType());
3538        o(" ");
3539        o(colname);
3540        o(" = bean.");
3541        o(wrangler.getGetName(cd));
3542        ol("();");
3543    
3544        /* we don't need this, if it's modified, then we
3545          save it, whether it was originally
3546          null in the db is irrelevant
3547         
3548        if (bean.isNullInDB(col_a))
3549        o(tabprefix2);
3550        o("if (bean.");
3551        o(wrangler.getIsNullInDBName(cd));
3552        ol("()) {");
3553        o(tabprefix3);
3554        o("ps.");
3555        o(cd.getPreparedStmtSetNullMethod("pos", colname));
3556        ol(";");
3557        o(tabprefix3);
3558        ol("}");
3559        o(tabprefix2);
3560        ol("else{");
3561        o(tabprefix3);
3562        */
3563        o(tabprefix2);
3564        ol(cd.getPreparedStmtSetMethod("ps.", "pos", colname));
3565        //ol("log.bug(ps);");       
3566        o(tabprefix2);
3567        ol("}");
3568        } //~for
3569      }
3570    
3571    
3572    void utilFillPStmtFromList_IfModified_Object 
3573      (final List list, final String tabprefix) 
3574    throws SQLException
3575      {
3576      final String tabprefix2 = tabprefix + "\t";
3577      final String tabprefix3 = tabprefix + "\t\t";
3578    
3579      o(tabprefix);
3580      ol("int pos = 0;");
3581    
3582      for (int n = 0; n < list.size(); n++)
3583        {
3584        ColumnData cd = (ColumnData) list.get(n); 
3585        String colname = cd.getName();
3586    
3587        o(tabprefix);
3588        o("if (bean.");
3589        o(wrangler.getIsModifiedName(cd));
3590        ol("()) {");
3591    
3592        if (! cd.usesPrimitiveJavaType()) 
3593          {
3594          o(tabprefix2);
3595          o ("if (bean.");
3596          o (wrangler.getGetName(cd));
3597          ol("() == null) { ");
3598          o (tabprefix3);
3599          ol("/* no value to set here, uses [xxx IS NULL] syntax*/");
3600          o (tabprefix3);
3601          ol("}");
3602          o(tabprefix2);
3603          ol("else{");
3604          }
3605          
3606        o(tabprefix3);
3607        ol("pos++;"); 
3608        o(tabprefix3);
3609        o(cd.getJavaTypeFromSQLType());
3610        o(" ");
3611        o(colname);
3612        o(" = bean.");
3613        o(wrangler.getGetName(cd));
3614        ol("();");
3615        o(tabprefix3);
3616        ol(cd.getPreparedStmtSetMethod("ps.", "pos", colname));
3617        o(tabprefix3);
3618        ol("}");
3619    
3620        if (! cd.usesPrimitiveJavaType()) {
3621          o(tabprefix2);
3622          ol("}");
3623          }
3624        } //~for
3625      }
3626    
3627    
3628    /**  
3629    Usage: 
3630    java fc.jdbc.dbobjects.Generate -conf
3631    <path-to-configuration-file> No flags will produce a list of
3632    options and usage information.
3633    **/
3634    public static void main(String[] args) throws Exception
3635      {
3636      Generate gen = new Generate(args);
3637      }
3638    
3639    }