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    <p>
067    <hr>
068    <h3>Notes</h3>
069    <b>Note 1</b>: This framework always retrieves and saves data directly to
070    and fro from the database and never caches data internally. This is a
071    design feature and keeps this framework orthogonal to caching
072    issues/implementations. The results returned by the framework can always
073    be cached as needed via say, the  {@link fc.util.cache.Cache} utility
074    class(es).
075    <p>
076    <b>Note 2</b>: MySQL 3.x, 4.x or 5.x does not have true boolean types and
077    silently converts bool types to TINYINT. This wreaks havoc with
078    auto-generated code which creates methods with the wrong signature
079    (TINYINT as opposed to bool).
080    <p>
081    There are 2 approaches to solving this mysql-specific problem: 
082    <blockquote>
083      a) Require all boolean columns to begin with some keyword (say bool_)
084      and if a column begins with this word, then treat it as a boolean,
085      regardless of the type returned by the database meta data.<br><br>
086      b) Treat all TINYINT's as boolean types. This is the approach I have
087      chosen since TINYINT's are NOT portable across databases (for example
088      PostgresQL does not have TINYINT's). Therefore we should not use
089      TINYINT's in physical database models; if booleans are turned into
090      TINYINT's by MySQL then so be it..since that will not clash with any of
091      our modelled types.
092    </blockquote>
093    If the flag <tt>mysqlBooleanHack</tt> is set to <tt>false</tt> in the
094    configuration file, then TINYINT's are <b>not</b> transformed to booleans.
095    There should be no practical need to do this however.
096    <p>
097    This program uses a user specified configuration file. The configuration
098    file allows for many code generation options. To see the configuration
099    help, invoke this program from the command line with no arguments, such
100    as:
101    <blockquote><pre>
102    java fc.jdbc.dbo.Generate
103    </pre></blockquote>
104    
105    @author hursh jain
106    **/
107    public final class Generate
108    {
109    static final String nl      = IOUtil.LINE_SEP;
110    static final String mgrSuffix   = "Mgr";
111    static final String DBO_BaseClassName =   "fc.jdbc.dbo.DBO";
112    static final String DBO_MgrBaseClassName =  "fc.jdbc.dbo.DBOMgr";
113    
114    java.util.Date    rundate;
115    DatabaseMetaData  md;
116    PropertyMgr     props;
117    SystemLog     log;
118    Connection      con;
119    String        url;
120    DBspecific      dbspecific;
121    File        outputDir;
122    String        catalogName;
123    String        packageName; 
124    String        classVis;
125    String        fieldVis;
126    boolean       accessors;
127    String[]      tableTypesToProcess;
128    List        tableNamesToInclude;
129    Map         tableNamesToIncludeAction;
130    final int     INCLUDE_ANY_PREFIX  = 1, 
131              INCLUDE_ANY_SUFFIX  = 2, 
132              INCLUDE_CONTAINS  = 3,
133              INCLUDE_EXACT     = 4;
134    Collection      tableNamesToExclude;
135    PrintWriter     out;
136    NameWrangle     wrangler;
137    
138    //TODO: These need to come from a i18n resource file
139    //validate errors/messages
140    String validateNull_ErrorMsg = "Error: Required field, please enter a value";
141    String validateIntegerOnly_ErrorMsg = "Error: Please enter only numbers in this field";
142    String validateText_ErrorMsg_MaxSize = "Not enough or too many characters";
143    
144    //list of tables in our database
145    List        tableList;
146    //used as a temp string buffer by various methods, can
147    //be reset by setLength(0);
148    StringBuffer    strbuf = new StringBuffer(2048);
149    
150    //Changed/set for the CURRENT TABLE being processed
151    String        beanClassName;         //FOO
152    String        mgrClassName;          //FOO_Mgr
153    Table       table;                 //foo
154    List        pklist;                //[a, b]
155    List        fklist;                //[b]
156    String        colsCommaDelimString;  //"a, b, c, d, e"
157    String        pkCommaDelimString;    //"a, b"
158    String        fkCommaDelimString;    //"b, c"
159    String        pkFormalParamString;   //"int a, byte b....."
160    
161    //not enabled -- don't think this is good
162    //boolean       modifiableAutoIncrementColumns;
163    /* taken out from the conf file:
164    generate.sets_for_auto_increment_columns
165      control whether auto_increment columns will be allowed to be modified
166      via setter accessor methods. By default this is false which implies
167      that set() methods will NOT be generated for auto increment columns.
168      Note: get() methods will always be generated, this only controls the
169      set() method generation and the default setting here is almost always
170      appropriate.
171    */
172    
173    /* 
174    INTERNAL NOTES:
175    - if a new mgr method is added, also update 
176    mgrWriteMethodFields and mgrWriteMethodStats
177    */
178    
179    public Generate(String[] args) throws Exception
180      {
181      log = Log.getDefault();
182      log.printTimestamp(true);
183      log.printRelativeTimestamp(true);
184    
185      //turn on to debug connection manager etc.
186      //log.setLevel(SystemLog.DEBUG);  
187    
188      Args myargs = new Args(args);
189      myargs.setDefaultUsage(this);
190      String conf   = myargs.getRequired("conf");
191    
192      props = new FilePropertyMgr(new File(conf));
193    
194      //default logging level for the rest of our app
195      String loglevel = props.get("log.level"); 
196    
197      if (loglevel != null) {
198        log.setLevel(loglevel);
199        }
200    
201      ConnectionMgr cmgr = new SimpleConnectionMgr(props);  
202    
203      String driver_name = cmgr.getDriver().getClass().getName();
204      DBName dbname = DBName.fromDriver(driver_name);
205      
206      if (dbname == null) {
207        log.bug("dbname=", dbname);
208        log.error("Could not understand the name of the target database. See documentation for more info.");
209        System.exit(1);
210        }
211      
212      dbspecific = (DBspecific) 
213              Class.forName("fc.jdbc.dbo.DB" + dbname.toString().toLowerCase())
214                .newInstance();
215      
216      url = cmgr.getURL();
217      con = cmgr.getConnection(); 
218      md = con.getMetaData(); 
219      rundate = new java.util.Date();
220    
221      String catalogName = props.get("jdbc.catalog");
222      if (catalogName == null) {
223        catalogName = "";
224        }
225    
226      //Generate options  
227      String output_dir = props.getRequired("generate.output.dir"); 
228      outputDir = new File(output_dir);
229    
230      if (! outputDir.isDirectory() || ! outputDir.canWrite())
231        {
232        log.error("Specified output location '" + output_dir + "' is not a directory and/or is not writable");
233        System.exit(1);
234        }
235    
236      if (! outputDir.exists()) 
237        {
238        System.out.print("Output directory: " + 
239                output_dir + " does not exist. Creating..");
240        outputDir.mkdirs();
241        System.out.println("..done");
242        }
243    
244      log.info("Output Directory: ", outputDir);
245    
246      /*
247      modifiableAutoIncrementColumns = 
248        Boolean.valueOf(
249          props.get("generate.sets_for_auto_increment_columns", "false")
250          ).booleanValue();
251      */
252      
253      packageName = props.get("generate.class_package");
254    
255      accessors = Boolean.valueOf(
256              props.get("generate.accessors", "true")).
257            booleanValue();
258    
259      classVis = props.get("generate.class_vis", "public");
260      fieldVis = (accessors) ?
261              props.get("generate.field_vis", "private") :
262              /* public by default if no accessors */
263              props.get("generate.field_vis", "public");
264    
265    
266      String tabletypes = props.get("target.types_to_process");
267      if (tabletypes != null) 
268        {
269        tableTypesToProcess = tabletypes.split(",\\s*");
270        for (int n=0; n < tableTypesToProcess.length; n++) {
271          tableTypesToProcess[n] = tableTypesToProcess[n].toUpperCase();
272          }
273        }
274    
275      String tableinclude = props.get("target.tables_to_process");
276      if (tableinclude != null) 
277        {
278        String[] temp = tableinclude.split(",\\s*");
279        tableNamesToInclude = new ArrayList();
280        tableNamesToIncludeAction = new HashMap();
281        for (int n = 0; n < temp.length; n++) 
282          {     
283          //we match table names from database to those specified
284          //in the config file, both are lowercased before
285          //matching
286          //trim() in case table_to_process="foo   "     
287          temp[n] = temp[n].trim().toLowerCase();
288    
289          boolean startsWithStar = false, endsWithStar = false;
290          if (temp[n].startsWith("*")) 
291            {
292            if (temp[n].length() == 1) {
293              throw new Exception("Bad option in config file.\nIn line: " + tableinclude + "\nA star must be a prefix/suffix to a tablename, not standalone.");
294              }
295            temp[n] = temp[n].substring(1, temp[n].length());
296            startsWithStar = true;
297            }
298          if (temp[n].endsWith("*")) 
299            {
300            if (temp[n].length() == 1) {
301              throw new Exception("Bad option in config file.\nIn line: " + tableinclude + "\nA star must be a prefix/suffix to a tablename, not standalone.");
302              }
303            temp[n] = temp[n].substring(0, temp[n].length()-1);
304            endsWithStar = true;
305            }
306          
307          if (startsWithStar) 
308            {
309            if (endsWithStar) { //both start/end star
310              tableNamesToIncludeAction.put(temp[n], INCLUDE_CONTAINS);
311              }
312            else{ //start_star
313              tableNamesToIncludeAction.put(temp[n], INCLUDE_ANY_PREFIX); 
314              }
315            }
316          else {
317            if (endsWithStar) { //end_star
318              tableNamesToIncludeAction.put(temp[n], INCLUDE_ANY_SUFFIX);
319              }
320            else{ //exact_match
321              tableNamesToIncludeAction.put(temp[n], INCLUDE_EXACT); 
322              }
323            }   
324          tableNamesToInclude.add(temp[n]);
325          }
326        
327        log.bug("Table names to include:", tableNamesToInclude);
328        log.bug("Table names to include action:", tableNamesToIncludeAction);
329        }
330        
331      String table_exclude = props.get("target.tables_to_ignore");
332      if (table_exclude != null) 
333        {
334        String[] temp = table_exclude.split(",\\s*");
335        tableNamesToExclude = new HashSet();
336        for (int n = 0; n < temp.length; n++) {
337          //we match table names from database to those specified
338          //in the config file, both are lowercased before
339          //matching
340          tableNamesToExclude.add(temp[n].trim().toLowerCase());
341          }
342        log.bug("Table names to exclude", tableNamesToExclude);
343        }
344    
345      wrangler = new NameWrangle(props, log);
346      
347      readTables();
348      generateCode();
349      cmgr.close();
350      }
351    
352    void readTables() throws IOException, SQLException
353      {
354      tableList = new ArrayList();
355      
356      Table.init(md, log, props, dbspecific);
357    
358      //not useful generally at least with mysql, postgresql
359      String schemaPattern = null;
360      //we get all tables, maybe if this is needed at all
361      //we can add this as a config file option
362      String tableNamePattern = "%";
363      
364      ResultSet rs = md.getTables(
365        catalogName, schemaPattern, 
366        tableNamePattern, tableTypesToProcess);
367      while (rs.next()) 
368        {
369        String tablename = rs.getString("TABLE_NAME");
370        //tablename cannot be null if the driver is unbroken
371        //but can it be an empty string ?
372        if (tablename.intern() == "") {
373          throw new SQLException("The returned tablename was an empty string, looks like the JDBC driver is broken");
374          }
375          
376        String lower_tablename = tablename.toLowerCase();
377    
378        boolean skip = false;
379        
380        if (tableNamesToInclude != null) 
381          {
382          skip = true;
383          //including only certain tables, if it is NOT in
384          //include list we continue to the next table
385          for (int p = 0; p < tableNamesToInclude.size(); p++) 
386            {
387            String  target = (String) tableNamesToInclude.get(p);
388            int   action = (Integer) tableNamesToIncludeAction.get(target);
389    
390            //target=foo*, lower_tablename = foo_xxx, foo etc
391            if (action == INCLUDE_ANY_SUFFIX) {  
392              if (lower_tablename.startsWith(target)) {
393                skip = false;
394                break;
395                }
396              }
397            //target=*foo, lower_tablename = xxx_foo, foo etc
398            else if (action == INCLUDE_ANY_PREFIX) { //*<tablee>
399              if (lower_tablename.endsWith(target)) {
400                skip = false;
401                break;
402                }
403              }
404            else if (action == INCLUDE_CONTAINS) {
405              if (lower_tablename.indexOf(target) != -1) {
406                skip = false;
407                break;
408                }         
409              }
410            else if (action == INCLUDE_EXACT) {
411              if (lower_tablename.equals(target)) {
412                skip = false;
413                break;
414                }         
415              }
416            else
417              throw new IOException("Internal error. Unrecognized action = " + action);
418            }
419          }
420    
421        if (skip) {
422          log.bug("Ignoring table: ", tablename);
423          continue;   
424          }
425        
426        if (tableNamesToExclude != null) {
427          //excluding certain tables, if it IS in exclude list
428          //we continue to next table
429          if (tableNamesToExclude.contains(lower_tablename)) {
430            log.bug("Ignoring exclude table: ", tablename);
431            continue;   
432            }
433          }
434    
435        log.info(">>>> Processing table: ", tablename);   
436    
437        String tabletype  = rs.getString("TABLE_TYPE");
438        String remarks    = rs.getString("REMARKS");
439    
440        Table table = new Table(con, 
441                catalogName, schemaPattern, 
442                tablename, tabletype, remarks
443                ); 
444        tableList.add(table);
445        } //~while rs.next
446      } //~process
447    
448    void generateCode() throws IOException, SQLException
449      {
450      for (int n = 0; n < tableList.size(); n++)
451        {
452        table = (Table) tableList.get(n);
453        pklist = (List) table.getPKList();
454        fklist = (List) table.getFKList();
455        
456        //list of pk's for this table as formal parameters
457        //in a generated method, for example:
458        //      int col_a, String col_b, Date col_c
459        int size = pklist.size();
460        StringBuffer buf = new StringBuffer(128);
461        for (int m = 0; m < size; m++)
462          {
463          ColumnData cd = (ColumnData) pklist.get(m);
464          buf.append(cd.getJavaTypeFromSQLType());
465          buf.append(" ");
466          buf.append(cd.getName());
467          if ( m < (size-1))
468            buf.append(", ");
469          }
470        pkFormalParamString = buf.toString();
471        
472        colsCommaDelimString = Table.getListAsString(table.getColumnList());
473        pkCommaDelimString = Table.getListAsString(pklist);
474        fkCommaDelimString = Table.getListAsString(fklist);
475        writeBeanCode();
476        writeMgrCode();
477        }
478      }
479    
480    void writeBeanCode() throws IOException, SQLException
481      {
482      beanClassName = wrangler.getClassName(table.getName());
483      String filename =  beanClassName + ".java";
484      File f = new File(outputDir, filename);
485    
486      /* this ensures that the new file will not have the same case as the
487      old file, jdk1.5 on osx 10.4 and possibly others keep the existing case 
488      of the file if it already exists.
489      */  
490      if (f.exists())
491        f.delete();
492    
493      out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
494    
495      writePackage();
496      writePrologue();
497      writeImports();
498      beanWriteClass();
499      out.close();
500      } 
501    
502    void writeMgrCode() throws IOException, SQLException
503      {
504      mgrClassName = 
505        wrangler.getClassName(table.getName()) + mgrSuffix;
506        
507      String filename = mgrClassName + ".java";
508      File f = new File(outputDir, filename);
509      
510      /* this ensures that the new file will not have the same case as the
511      old file, jdk1.5 on osx 10.4 and possibly others keep the existing case 
512      of the file if it already exists.
513      */  
514      if (f.exists())
515        f.delete();
516        
517      out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
518      
519      writePackage();
520      writePrologue();
521      writeImports();
522      mgrWriteClass();
523      out.close();
524      } 
525      
526    
527    void writePrologue() 
528      {
529      String name = getClass().getName();
530      ol("/*");
531      ol(" * Auto generated on: " + rundate);
532      ol(" * JDBC url: [" + url + "]");
533      ol(" * WARNING: Manual edits will be lost if/when this file is regenerated.");
534      ol(" */");
535      }
536    
537    void writePackage() 
538      {
539      if (packageName != null) 
540        {
541        ol("package " + packageName + ";");
542        ol();
543        }
544      }
545        
546    void writeImports() 
547      {
548      ol("import java.io.*;");
549      ol("import java.math.*;");
550      ol("import java.sql.*;");
551      ol("import java.util.*;");
552      ol();
553      ol("import fc.io.*;");
554      ol("import fc.jdbc.*;");
555      ol("import fc.jdbc.dbo.*;");
556      ol("import fc.util.*;");
557      ol("import fc.web.forms.*;");
558      ol();
559      }
560      
561    
562    /** 
563    returns the comment used for get/set methods and
564    for fields in the bean class 
565    */
566    final HashMap commentMap = new HashMap();
567    String getBeanComment(ColumnData item) throws SQLException
568      {
569      String comment = (String) commentMap.get(item);
570      if (comment != null) 
571        return comment;
572      
573      comment = 
574        "/** " + item.getSQLTypeDriverSpecificName() +
575            " (" + item.getSQLTypeName() + ")"; 
576      if (item.isPK()) {
577        comment += "; PK=yes";
578        }
579      if (item.isFK()) {
580        ColumnData.FKData fkdata = item.getFK();
581        comment += "; FK=yes, refers to: " 
582          + fkdata.getPKTableName() + "." 
583          + fkdata.getPKColName();
584        } 
585      comment += "; Nullable=" + item.isNullable(); 
586      comment += "; AutoInc=" + item.isAutoIncrement();
587      comment += "; MaxSize=" + item.getSize();
588      if (item.hasRemarks()) {
589        comment += "; Remarks: " + item.getRemarks();
590        }
591      comment += " */";
592      
593      commentMap.put(item, comment);
594      return comment;
595      }
596    
597    
598    void beanWriteClass() throws SQLException
599      {
600      ol("/**");
601      o ("Represents a row in the  ");
602      o ( table.getName());
603      ol(" table. ");
604      
605      String remarks = table.getRemarks(); 
606      if ( remarks != null)
607        {
608        ol("<p><b>Table Remarks: </b>");    
609        ol(remarks);
610        }
611    
612      ol("*/");
613      o(classVis + " class ");
614      o(beanClassName); //classname 
615      o(" extends ");
616      ol(DBO_BaseClassName);
617      ol("{");
618        
619      beanWriteConstructor();
620      beanWriteDBFields();
621      beanWriteDBFieldsTracking();
622      beanWriteMiscMethods();
623      beanWriteGetSet();  
624      ol("}");
625      }
626    
627    void beanWriteConstructor() {
628      ol("/* Default constructor */");
629      o(classVis);
630      o(" ");
631      o(beanClassName);
632      ol("()");
633      ol("  {");
634      ol("  this.__isNew  = true;");
635      ol("  }");
636      ol();
637      }
638    
639    void beanWriteDBFields() throws SQLException
640      {
641      List cols = table.getColumnList();
642    
643      ol("/*--------------------- Columns ------------------------*/");
644      //write database fields
645      TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();
646      config.setPrintBorders(false);
647      config.setCellSpacing(1);
648      config.setCellPadding(0);
649      config.setAutoFit(true);
650      TablePrinter p = new TablePrinter(4, out, config);
651      p.startTable();
652      for (int n = 0; n < cols.size(); n++)
653        {
654        ColumnData cd = (ColumnData) cols.get(n);
655        log.bug("Processing column: ", cd);
656        p.startRow();
657        p.printCell(fieldVis);
658        p.printCell(cd.getJavaTypeFromSQLType());
659        p.printCell(cd.getName() + ";");
660        p.printCell(getBeanComment(cd));  
661        p.endRow();
662        }
663      p.endTable();   
664      ol("/*------------------------------------------------------*/");
665      }
666      
667    void beanWriteDBFieldsTracking()
668      {
669      List cols = table.getColumnList();
670    
671      ol();
672      ol("    boolean __force_update  = false;");
673      ol("private Map   __extra_data;     ");
674      ol("private boolean __isNew     = false;");
675      ol("private boolean __isModified  = false;");
676      for (int n = 0; n < cols.size(); n++)
677        {
678        //modified column ?
679        ol();
680        ColumnData cd = (ColumnData) cols.get(n);
681        String colname = cd.getName();
682        o("private   boolean __isModified_");
683        o(colname);
684        ol(" = false;");
685        o("protected boolean __isNullInDB_");
686        o(colname);
687        ol(" = false;");
688        o("/**returns <tt>true</tt> if ");
689        o(colname);
690        o(" has changed since it was created/loaded, <tt>false</tt> otherwise");
691        ol("*/");
692        o("public boolean ");
693        o(wrangler.getIsModifiedName(cd));
694        o("() { return this.__isModified_");
695        o(colname);
696        ol("; }");
697        //column null in database ?
698        o("/**returns <tt>true</tt> if ");
699        o(colname);
700        o(" is null in the database");
701        ol("*/");
702        o("public boolean ");
703        o(wrangler.getIsNullInDBName(cd));
704        o("() { return this.__isNullInDB_");
705        o(colname);
706        ol("; }");
707        }
708        }
709    
710    void beanWriteMiscMethods() throws SQLException
711      {
712      ol();
713      //isnew
714      ol();
715      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 */");
716      ol("public boolean isNew() ");
717      ol("  {");
718      ol("  return this.__isNew;");
719      ol("  }");
720        
721      ol();
722      ol("/** Specify <tt>true</tt> to set this object's status as newly created (and not read from a database) */");
723      ol("protected void setNew(boolean val) ");
724      ol("  {");
725      ol("  this.__isNew = val;");
726      ol("  }");
727        
728      //modified
729      ol();
730      ol("/** returns <tt>true</tt> if this object's data (for any field) has changed since it was created/loaded, <tt>false</tt> otherwise */");
731      ol("public boolean isModified() ");
732      ol("  {");
733      ol("  return this.__isModified;");
734      ol("  }");
735      
736      ol();
737      ol("/** Resets the modified status of this object to not-modified");
738      ol("this is useful when loading an object via a prepared statement");
739      ol("(by using various setXXX methods when we do so, we inadvertently");
740      ol("set the modified status of each field to true)");
741      ol("*/"); 
742      ol("void resetModified() ");
743      ol("  {");
744      List collist = table.getColumnList();
745      ol("  this.__isModified = false;");
746      for (int n = 0; n < collist.size(); n++)
747        {
748        ColumnData cd = (ColumnData) collist.get(n);
749        String colname = cd.getName();
750        o(" this.__isModified_");
751        o(colname);
752        ol(" = false;");
753        } 
754      ol("  }");
755          
756      ol();
757      ol("/**");
758      ol("Allows putting arbitrary object-specific data into this object.");
759      ol("This is useful to store additional query-specific columns when performing");
760      ol("arbitrary queries. For example: ");
761      ol("<blockquote><pre>");
762      o ("String query = \"select <font color=blue>1+1 as my_sum, \n\t\tnow() as my_time</font>, ");
763      o (table.getName());
764      o ("Mgr.columns() \n\t\tfrom ");
765      o (table.getName());
766      ol("\";");
767      ol("PreparedStatement ps = con.prepareStatment(query);");
768      ol("ResultSet rs = ps.executeQuery();");
769      ol("List list = new ArrayList();");
770      ol("while (rs.next()) {");
771      o ("  <font color=green>"); o (table.getName()); o ("</font> obj = "); 
772      o(table.getName()); 
773      ol("Mgr.getFromRS(rs);"); 
774      ol("  obj.<font color=blue>putExtraData(\"my_sum\"</font>, rs.getInt(\"my_sum\"));");
775      ol("  obj.<font color=blue>putExtraData(\"my_time\"</font>, rs.getDate(\"my_time\"));");
776      ol("  }");
777      o ("//use the list later on...each <font color=green>");
778      o (table.getName());
779      ol(" </font>object in the list will ");
780      ol("//have the extra data..");
781      ol("</pre></blockquote>");
782      ol("*/");
783      ol("public void putExtraData(Object key, Object value) ");
784      ol("  {");
785      ol("  synchronized (this) {");
786      ol("    if (__extra_data == null) {");
787      ol("      __extra_data = new HashMap();");
788      ol("      }");
789      ol("    }");
790      ol("  __extra_data.put(key, value);");
791      ol("  }");
792    
793      ol();
794      ol("/**");
795      ol("Allows retrieving arbitrary object-specific data from this object.");
796      ol("This data should have been put via the {@link #putExtraData putExtraData} ");
797      ol("method prior to invoking this method");
798      ol("*/");
799      ol("public Object getExtraData(Object key) ");
800      ol("  {");
801      ol("  synchronized (this) {");
802      ol("    if (__extra_data == null) {");
803      ol("      return null;");
804      ol("      }");
805      ol("    }");
806      ol("  return __extra_data.get(key);");
807      ol("  }");
808          
809      //toString    
810      ol();
811      ol("public String toString() ");
812      ol("  {");
813      ol("  final String nl = fc.io.IOUtil.LINE_SEP;");
814      ol("  StringBuffer buf = new StringBuffer(256);");
815      o ("  buf.append(\"Class Name: [");
816      o (beanClassName);
817      ol("]\");");
818      ol("  buf.append(\" [isDiscarded=\").append(this.isDiscarded()).append(\"]\");");
819      ol("  buf.append(\" [isNew=\").append(this.isNew()).append(\"]\");");
820      ol("  buf.append(\" [isModified=\").append(this.isModified()).append(\"]\");");
821      ol("  buf.append(nl);");
822      ol("  buf.append(\"Note: IsNullInDB only meaningful for existing rows (i.e., isNew=false)\");");
823      ol("  buf.append(nl);");
824      ol();
825      ol("  ByteArrayOutputStream out = new ByteArrayOutputStream(768);");
826      ol("  TablePrinter.PrintConfig config = new TablePrinter.PrintConfig();");
827      ol("  config.setPrintBorders(false);");
828      ol("  config.setCellSpacing(1);");
829      ol("  config.setCellPadding(0);");
830      ol("  config.setAutoFit(true);");
831      ol("  TablePrinter p = new TablePrinter(7, new PrintStream(out), config);");
832      ol("  p.startTable();");
833      ol();
834      ol("  p.startRow();");
835      ol("  p.printCell(\"PK\");");
836      ol("  p.printCell(\"FK\");");
837      ol("  p.printCell(\"Field\");");
838      ol("  p.printCell(\"Value\");");
839      ol("  p.printCell(\"isModified\");");
840      ol("  p.printCell(\"isNullinDB\");");
841      ol("  p.printCell(\"isSerial/AutoInc\");"); 
842      ol("  p.endRow();");
843      
844      for (int n = 0; n < collist.size(); n++)
845        {
846        ColumnData cd = (ColumnData) collist.get(n);
847        String isPK   = cd.isPK() ? "x" : "-";
848        String isFK   = "-";
849        if (cd.isFK()) {
850          ColumnData.FKData fk = cd.getFK();
851          isFK = "x [" +  fk.getPKTableName()  + "." + 
852                  fk.getPKColName() + "]"; 
853                  
854          }
855        String colname  = cd.getName();
856        String value  = "String.valueOf(this." + colname + ")";
857        String modified = "(this.__isModified_" + colname + ")";
858        String isnull = "(this.__isNullInDB_" + colname + ")";
859        String isAI   = cd.isAutoIncrement() ? "x" : "-";
860    
861        ol();
862        ol("  p.startRow();");
863        o ("  p.printCell(\""); o(isPK);  ol("\");");
864        o ("  p.printCell(\""); o(isFK);  ol("\");");
865        o ("  p.printCell(\""); o(colname);  ol("\");");
866        o ("  p.printCell(");   o(value);    ol(");");
867        o ("  p.printCell(");   o(modified); ol(" ?\"x\":\"-\");");
868        o ("  p.printCell(");   o(isnull);   ol(" ?\"x\":\"-\");");
869        o ("  p.printCell(\""); o(isAI);  ol("\");");
870        ol("  p.endRow();");
871        }
872      ol();
873      ol("  p.endTable();");
874      ol("  buf.append(out.toString());");
875      ol("  return buf.toString();");
876      ol("  }");
877      }
878    
879    void beanWriteGetSet() throws SQLException
880      {
881      List cols = table.getColumnList();
882    
883      ol("/* ============== Gets and Sets ============== */");
884      for (int n = 0; n < cols.size(); n++)
885        {
886        ColumnData cd = (ColumnData) cols.get(n);
887        ol();
888        ol(getBeanComment(cd));
889        String colname = cd.getName();
890        o("public ");
891        o(cd.getJavaTypeFromSQLType());
892        o(" ");
893        o(wrangler.getGetName(cd));
894        ol("()  { ");
895        o(" return ");
896        o(colname);
897        ol(";");
898        ol("  }");
899      
900        ol();
901        ol(getBeanComment(cd));
902        if (cd.isAutoIncrement()) {
903          //we need to generate set with package access
904          //to set the id when loading the object internally
905          //from a result set
906          o("/* Generating set for ");
907          o(wrangler.getSetName(cd));
908          ol(" with non public access since it's an auto-increment column */");
909          }
910        else {
911          o("public");
912          } 
913        o(" ");
914        o(beanClassName);
915        o(" ");
916        o(wrangler.getSetName(cd));
917        o("(");
918        o(cd.getJavaTypeFromSQLType());
919        o(" ");
920        o(colname);
921        ol(") {");
922        ol("  this." + colname + " = " + colname + ";"); 
923        ol("  this.__isModified_" + colname + " = true;" );
924        ol("  this.__isModified = true;" );
925        ol("  return this;");
926        ol("  }");
927        } 
928      }
929      
930    void mgrWriteClass() throws SQLException
931      {
932      //classname
933      ol("/**");
934      o("Manages various operations on the ");
935      o(table.getName());
936      ol(" table. ");
937      ol("<p>Most methods of this class take a {@link java.sql.Connection Connection}");
938      ol("as an argument and use that connection to run various queries. ");
939      ol("The connection parameter is never closed by methods in this class and that connection");
940      ol("can and should be used again. Methods of this class will also throw a <tt>IllegalArgumentException</tt>");
941      ol("if the specified connection object is <tt>null</tt>");
942      ol();
943      ol("<p>Thread Safety: Operations on this class are by and large thread safe in that");
944      ol("multiple threads can call the methods at the same time. However, seperate threads");
945      ol("should use seperate connection objects when invoking methods of this class");  
946      ol("*/");
947      o(classVis + " final class " + mgrClassName);
948      o(" extends ");
949      ol(DBO_MgrBaseClassName);
950      ol("{");
951      
952      mgrWriteFields();
953      mgrWriteConstructor();
954      mgrWriteMethods();  
955      ol("}");
956      }
957    
958    void mgrWriteConstructor()
959      {
960      ol();
961      ol("/** Constructor is private since class is never instantiated */");
962      o("private ");
963      o(mgrClassName);
964      ol("() {");
965      ol("\t}");
966      ol();
967      }
968    
969    void mgrWriteFields() throws SQLException
970      {
971      ol("/* --- Fields used for collecting usage statistics --- ");
972      ol("Increments to these don't need to be synchronized since these are");
973      ol("ints and not longs and memory visibility is not an issue in the");
974      ol("toString() method (in which these are read).");
975      ol("*/");
976      ol("private static int __getall_called = 0;");
977      ol("private static int __getlimited_called = 0;");
978      ol("private static int __getbykey_called = 0;");
979      ol("private static int __getwhere_called = 0;");
980      ol("private static int __getusing_called = 0;");
981      ol("private static int __getusing_ps_called = 0;");
982      ol("private static int __getfromrs_called = 0;");
983      ol("private static int __save_called = 0;");
984      ol("private static int __delete_called = 0;");
985      ol("private static int __deletebykey_called = 0;");
986      ol("private static int __deletewhere_called = 0;");
987      ol("private static int __deleteusing_called = 0;");
988      ol("private static int __count_called = 0;");
989      ol("private static int __countwhere_called = 0;");
990      ol("private static int __countusing_called = 0;");
991      ol("private static int __exists_called = 0;");
992      ol("/* -------------- end statistics fields -------------- */");
993      }
994    
995    void mgrWriteMethods() throws SQLException
996      {
997      //get
998      mgrWriteMethodGetAll();
999      mgrWriteMethodGetAllNoClause();
1000      mgrWriteMethodGetLimited();
1001      mgrWriteMethodGetByKey();
1002      mgrWriteMethodGetWhere();
1003      mgrWriteMethodGetUsing();
1004      mgrWriteMethodGetUsingNoClause();
1005      mgrWriteMethodGetUsingPS();
1006      mgrWriteMethodGetColumnNames();
1007      mgrWriteMethodGetFromRS();
1008      mgrWriteMethodDecodeFromRS();
1009      //save
1010      mgrWriteMethodSave();
1011      mgrWriteMethodUpdate();
1012      //delete
1013      mgrWriteMethodDelete();
1014      mgrWriteMethodDeleteByKey();
1015      mgrWriteMethodDeleteUsing();
1016      mgrWriteMethodDeleteWhere();
1017      //count, exists, misc.
1018      mgrWriteMethodCount();
1019      mgrWriteMethodCountWhere();
1020      mgrWriteMethodCountUsing();
1021      mgrWriteMethodExists();
1022      mgrWriteMethodPrepareStatement();
1023      mgrWriteCheckDiscarded();
1024      mgrWriteMethodStats();
1025      mgrWriteMethodToString();
1026      //validation stuff
1027      mgrWriteValidators(); 
1028      }
1029    
1030    /*
1031    We return a list for no particular reason, it may have
1032    been better to return a bean_type[] instead, which would
1033    save typing casts. Testing shows that:
1034    
1035    bean_type[] arr = (bean_type[]) list.toArray(new bean_type[]);
1036    
1037    takes 1-2 ms for a 1000 elements so speed is not an issue 
1038    (although space might be since the list.toArray is newly 
1039    created).
1040    */
1041    final void mgrWriteMethodGetAll()
1042      {
1043      ol();
1044      o("static private final String getAllStmt = \"SELECT "); 
1045      o(colsCommaDelimString);
1046      o(" from ");
1047      o(table.getName());
1048      ol("\";");
1049      ol("/** ");
1050      ol("Returns all rows in the table. Use with care for large tables since");
1051      ol("this method can result in VM out of memory errors. <p>This method");
1052      ol("also takes an optional (can be null) <tt>clause</tt> parameter which");
1053      ol("is sent as is to the database. For example, a clause can be:");
1054      ol("<blockquote><pre>");
1055      ol("order by some_column_name");
1056      ol("</pre> </blockquote>");   
1057      o("@return  a list containing {@link ");
1058      o(beanClassName);
1059      o(" } objects <i>or an empty list</i> if there are no rows in the database");
1060      ol("*/");
1061      ol("public static List getAll(final Connection con, final String clause) throws SQLException");
1062      ol("  {");  
1063      ol("  __getall_called++;");
1064      ol("  final List list = new ArrayList();");
1065      //prepared statement has no parameters to set, used for
1066      //caching advantage only, (thus, no need to clear params)
1067      ol("  final String getAllStmtClaused = (clause == null) ? ");
1068      ol("               getAllStmt : getAllStmt + \" \" + clause;");
1069      ol("  PreparedStatement ps = prepareStatement(con, getAllStmtClaused);");
1070      ol("  log.bug(\"Query to run: \", ps);");
1071      ol("  final ResultSet rs = ps.executeQuery();");
1072      ol("  while (true) {");
1073      ol("    " + beanClassName +  " bean = decodeFromRS(rs);");
1074      ol("    if (bean == null) { break; } ");
1075      ol("    list.add(bean);");
1076      ol("    }");
1077      ol("  rs.close();");
1078      ol("  return list;"); 
1079      ol("  }");
1080      } //~write getall
1081    
1082    final void mgrWriteMethodGetAllNoClause()
1083      {
1084      ol();
1085      ol("/** ");
1086      o ("Convenience method that invokes {@link getAll(Connection, ");
1087      o (beanClassName);
1088      ol(", String) getAll} with an empty additional clause.");
1089      ol("*/");
1090      o ("public static List getAll(final Connection con) ");
1091      ol("throws ValidateException, SQLException");
1092      ol("  {");
1093      ol("  return getAll(con, null);");
1094      ol("  }");
1095      } //~write getUsing
1096    
1097    final void mgrWriteMethodGetLimited()
1098      {
1099      ol();
1100      o("static private final String getLimitedStmt = \"SELECT "); 
1101      o(colsCommaDelimString);
1102      o(" from ");
1103      o(table.getName());
1104      ol("\";");
1105      ol("/** ");
1106      ol("Returns all rows in the table starting from some row number and limited");
1107      ol("by a certain number of rows after that starting row. ");
1108      ol("<p>");
1109      ol("This method takes a required (non-null) <code>order_clause</code>, since when using");
1110      ol("a limit clause, rows must be ordered for the limit to make sense. The");
1111      ol("clause should be of the form <font color=blue>order by ...</font>");
1112      ol("<p>");
1113      ol("The <code>limit</code> specifies the number of rows that will be returned. (those many");
1114      ol("or possibly lesser rows will be returned, if the query itself yields less");
1115      ol("rows).");
1116      ol("<p>");
1117      ol("The <code>offset</code> skips that many rows before returning rows. A zero offset is");
1118      ol("the same as a traditional query with no offset clause, where rows from");
1119      ol("the beginning are returned. If say, offset = 10, then rows starting from");
1120      ol("row 11 will be returned.");
1121      ol("<p>");
1122      ol("The sql-query generated by this method is database specific but will (typically) look like:");
1123      ol("<blockquote><pre>");
1124      ol("select &lt;column_list&gt; from &lt;table&gt; order by &lt;clause&gt; limit 5 offset 10");
1125      ol("</pre> </blockquote>");   
1126      o("@return  a list containing {@link ");
1127      o(beanClassName);
1128      o(" } objects <i>or an empty list</i> if there are no rows in the database");
1129      ol("*/");
1130      ol("public static List getLimited(final Connection con, final String order_clause, int limit, int offset) throws SQLException");
1131      ol("  {");  
1132      ol("  __getlimited_called++;");
1133      ol("  final List list = new ArrayList();");
1134      //prepared statement has no parameters to set, used for
1135      //caching advantage only, (thus, no need to clear params)
1136      ol("  final String tmp = getLimitedStmt + \" \" + order_clause + \" LIMIT \" + limit + \" OFFSET \" + offset;");
1137      ol("  PreparedStatement ps = prepareStatement(con, tmp);");
1138      ol("  log.bug(\"Query to run: \", ps);");
1139      ol("  final ResultSet rs = ps.executeQuery();");
1140      ol("  while (true) {");
1141      ol("    " + beanClassName +  " bean = decodeFromRS(rs);");
1142      ol("    if (bean == null) { break; } ");
1143      ol("    list.add(bean);");
1144      ol("    }");
1145      ol("  rs.close();");
1146      ol("  return list;"); 
1147      ol("  }");
1148      } //~write getlimited
1149    
1150    final void mgrWriteMethodGetByKey() throws SQLException
1151      {
1152      ol();
1153      if (pklist.size() == 0) {
1154        ol("/* getByKey() not implemented since this table does not have any primary keys defined */");
1155        return;
1156        }
1157    
1158      o("static private final String getByPKStmt = \"SELECT "); 
1159      o(colsCommaDelimString);
1160      o(" from ");
1161      o(table.getName());
1162      o(" WHERE ");
1163      o(Table.getPreparedStmtPlaceholders(pklist));
1164      ol("\";");
1165      ol("/** ");
1166      ol("Returns <b>the</b> row corresponding to the specified primary key(s) of this table ");
1167      ol("or <b><tt>null</tt></b> if no row was found.");
1168      ol("<p>This method uses a prepared statement and is safe from SQL injection attacks");
1169      ol("*/");
1170      o("public static ");
1171      o(beanClassName);
1172      o(" getByKey(final Connection con, ");
1173      o(pkFormalParamString);
1174      ol(") throws SQLException");
1175      ol("  {");
1176      ol("  __getbykey_called++;");
1177      ol("  PreparedStatement ps = prepareStatement(con, getByPKStmt);");
1178      ol("  StringBuilder buf = null;");
1179      ol();
1180      for (int n = 0; n < pklist.size(); n++)
1181        {
1182        ColumnData cd = (ColumnData) pklist.get(n); 
1183        if (! cd.usesPrimitiveJavaType())
1184          {
1185          o ("  if ("); o(cd.getName()); ol(" == null) {");
1186          ol("    if (buf == null) { buf = new StringBuilder(); }");    
1187          o ("    buf.append(\"");
1188          o (cd.getName());
1189          ol(" was set to null (but is non-nullable)\").append(IOUtil.LINE_SEP);");
1190          ol("    }");
1191          ol();
1192          }
1193        String varname = cd.getName();
1194        o("\t");
1195        String pos = String.valueOf((n+1));
1196        ol(cd.getPreparedStmtSetMethod("ps.", pos, varname) );
1197        }
1198      
1199      ol("  if (buf != null) {");
1200      ol("    throw new ValidateException(buf.toString());"); 
1201      ol("    }");
1202      ol("  final ResultSet rs = ps.executeQuery();");
1203      ol("  log.bug(\"Query to run: \", ps);");
1204      ol("  " + beanClassName + " bean = decodeFromRS(rs);");
1205      ol("  rs.close();");
1206    //  ol("  ps.clearParameters();");
1207      ol("  return bean;");
1208      ol("  }");
1209      } //~write getbykey
1210      
1211    final void mgrWriteMethodGetWhere() throws SQLException
1212      {
1213      ol();
1214      ol("/** ");
1215      ol("Returns the rows returned by querying the table with the specified");
1216      ol("<tt>WHERE</tt> clause or <i>an empty list</i> if no rows were found.");
1217      ol("(note: the string <tt>\"WHERE\"</tt> does <b>not</b> have to be");
1218      ol("specified in the clause. It is added automatically by this method).");
1219      ol("<p>Queries can use database functions such as: <code>lower()</code>,");
1220      ol("<code>upper()</code>, <code>LIKE</code> etc. For example:");
1221      o ("<pre><blockquote>"); o(table.getName());
1222      ol("Mgr.getWhere(\"lower(col_a) = 'foo'\")");
1223      ol("//compares the lower case value of col_a with the string 'foo'");
1224      ol("</blockquote></pre>");  
1225      ol("<p><b>The \"where\" clause is sent as-is to the database</b>. SQL");
1226      ol("injection attacks are possible if it is created as-is from a <b><u>untrusted</u></b> source.");
1227      ol();
1228      ol("@throws IllegalArgumentException if the specified <tt>where</tt> parameter is null"); 
1229      ol("*/");
1230      ol("public static List getWhere(final Connection con, final String where) throws SQLException");
1231      ol("  {");
1232      ol("  __getwhere_called++;");
1233      ol("  Argcheck.notnull(where, \"the where parameter was null (and should not be null)\");");
1234      ol("  final String where_stmt = \"SELECT " + colsCommaDelimString + " from " + table.getName() + " WHERE \" + where ;");
1235      ol("  Statement stmt = QueryUtil.getRewindableStmt(con);");
1236      ol("  log.bug(\"Query to run: \", stmt, \" \", where_stmt);");
1237      ol("  final List list = new ArrayList();");
1238      ol("  final ResultSet rs = stmt.executeQuery(where_stmt);");
1239      ol("  while (true) {");
1240      ol("    " + beanClassName +  " bean = decodeFromRS(rs);");
1241      ol("    if (bean == null) { break; } ");
1242      ol("    list.add(bean);");
1243      ol("    }");
1244      ol("  stmt.close();");
1245      ol("  return list;"); 
1246      ol("  }");
1247      } //~write getwhere
1248    
1249    final void mgrWriteMethodGetUsing() throws SQLException
1250      {
1251      ol();
1252      ol("/** ");
1253      ol("Returns the rows returned by querying the table with the value of the");
1254      o ("specified <tt>");
1255      o (beanClassName);
1256      ol("</tt> object or <i>an empty list</i> if no rows were found. As many");
1257      ol("fields in <tt>alltypes</tt> can be set as needed and the values of");
1258      ol("all set fields (including fields explicitly set to <tt>null</tt>)");
1259      ol("are then used to perform the query.");
1260      ol("<p>");
1261      ol("This method is often convenient/safer than the {@link #getWhere");
1262      ol("getWhere} method (because the <tt>getWhere</tt> method takes an");
1263      ol("arbitrary query string which has to be properly escaped by the");
1264      ol("user)."); 
1265      ol("<p>Essentially, this method is a more convenient way to use a");
1266      ol("PreparedStatement. Internally, a prepared statement is created and");
1267      ol("it's parameters are set to fields that are set in this object).");
1268      ol("Using PreparedStatements directly is also perfectly fine. For");
1269      ol("example, the following are equivalent. ");
1270      ol("<p> Using a PreparedStatement:");
1271      ol("<blockquote><pre>");
1272      ol("String foo = \"select * from table_foo where x = ? and y = ?\";");
1273      ol("PreparedStatement ps = con.prepareStatement(foo);");
1274      ol("ps.setString(1, \"somevalue\");");
1275      ol("ps.setString(2, \"othervalue\");");
1276      ol("ResultSet rs  = ps.executeUpdate();");
1277      ol("while (rs.next()) {");
1278      ol("    table_foo bean = table_fooMgr.getFromRS(rs);");
1279      ol("    }");
1280      ol("</pre> </blockquote>");
1281      ol("");
1282      ol("Using this method:");
1283      ol("<blockquote><pre>");
1284      ol("table_foo <font color=blue>proto</font> = new table_foo();");
1285      ol("proto.set_x(\"somevalue\"); //compile time safety");
1286      ol("proto.set_y(\"othervalue\");  //compile time safety");
1287      ol("List beans = table_fooMgr.<font color=blue>getUsing(proto)</font>;");
1288      ol("</pre> </blockquote>"); 
1289      ol("<p>This method also takes an <tt>clause</tt> parameter which");
1290      ol("is sent as is to the database. For example, a clause can be:");
1291      ol("<blockquote><pre>");
1292      ol("order by some_column_name");
1293      ol("</pre> </blockquote>"); 
1294      ol("This clause is optional. Specify <tt>null</tt> to not use it at all.");
1295      ol("<p>Note: For a <i>very</i> large number of rows, it may be more");
1296      ol("efficient to use a prepared statement directly (as opposed to using");
1297      ol("this method). In most cases, this is not something to worry about,");
1298      ol("but your mileage may vary...");
1299      ol("*/");
1300      o("public static List getUsing(final Connection con, final ");
1301      o(beanClassName);
1302      ol(" bean, final String clause) throws ValidateException, SQLException");
1303      ol("  {");
1304      ol("  __getusing_called++;");
1305      ol("  Argcheck.notnull(bean, \"the bean parameter was null (and should not be null)\");");
1306      ol("  if (! bean.isModified()) { ");
1307      ol("    throw new ValidateException(\"bean=\" + bean + \" not modified, ignoring query\");");
1308      ol("    }");
1309      ol();
1310      ol("  int count = 0;");
1311      ol("  final StringBuilder buf = new StringBuilder(512);");
1312      ol("  buf.append(\"SELECT " + colsCommaDelimString + " from " +
1313            table.getName() + " WHERE \");");
1314    
1315      List cols = table.getColumnList();
1316      //we allow any col to be set, including pk or partial pk  
1317      List relevant_cols = cols; 
1318    
1319      for (int n = 0; n < cols.size(); n++) 
1320        {
1321        ColumnData cd = (ColumnData) cols.get(n); 
1322      
1323        o ("  if (bean.");
1324        o (wrangler.getIsModifiedName(cd));
1325        ol("()) { ");
1326        if (! cd.usesPrimitiveJavaType()) {
1327          o ("    if (bean."); o(wrangler.getGetName(cd)); ol("() == null) {");
1328          o ("      buf.append(\""); o(cd.getName()); ol(" is NULL and \");");
1329          ol("      }");
1330          ol("    else{");
1331          o ("      buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
1332          ol("      count++;");
1333          ol("      }");
1334          }
1335        else{
1336          o ("    buf.append(\""); o (cd.getName()); o ("=? and "); ol("\");"); 
1337          ol("    count++;");
1338          }
1339        ol("    }");
1340        }   
1341      
1342      ol();
1343      //get rid of last "and "
1344      ol("  buf.setLength(buf.length() - 4);"); 
1345      ol();
1346      ol("  if (clause != null) {");
1347      ol("    buf.append(\" \");");
1348      ol("    buf.append(clause);");
1349      ol("    }");
1350      ol();
1351      ol("  final String getUsingPKStmt = buf.toString();");
1352      ol("  PreparedStatement ps = prepareStatement(con, getUsingPKStmt);");
1353    
1354      utilFillPStmtFromList_IfModified_Object(relevant_cols, "\t");
1355    
1356      ol("  log.bug(\"Query to run: \", ps);");
1357      ol("  final List list = new ArrayList();");
1358      ol("  final ResultSet rs = ps.executeQuery();");
1359      ol("  while (true) {");
1360      ol("    " + beanClassName +  " row = decodeFromRS(rs);");
1361      ol("    if (row == null) { break; } ");
1362      ol("    list.add(row);");
1363      ol("    }");
1364      ol("  rs.close();");
1365    //  ol("  ps.clearParameters();");
1366      ol("  return list;"); 
1367      ol("  }");
1368      } //~write getusing
1369    
1370    final void mgrWriteMethodGetUsingNoClause() throws SQLException
1371      {
1372      ol();
1373      ol("/** ");
1374      o ("Convenience method that invokes {@link getUsing(Connection, ");
1375      o (beanClassName);
1376      o (", String) getUsing} with an empty <i><tt>clause</tt></i> parameter.");
1377      ol("*/");
1378      o("public static List getUsing(final Connection con, final ");
1379      o(beanClassName);
1380      ol(" bean) throws ValidateException, SQLException");
1381      ol("  {");
1382      ol("  return getUsing(con, bean, null);");
1383      ol("  }");
1384      } //~write getUsingnoclause
1385    
1386    //the actual method written is called getUsing(..) not getUsingPS(..)
1387    //i.e., getUsing is overloaded
1388    final void mgrWriteMethodGetUsingPS() throws SQLException
1389      {
1390      ol("/**");
1391      ol("This is a <i>convenience</i> method that runs the specified ");
1392      ol("prepared statement to perform a arbitrary query. For example: ");
1393      ol("<blockquote>");
1394      ol("<pre>");
1395      ol("PreparedStatement <font color=blue>ps</font> = some_tableMgr.prepare(con, ");
1396      ol("    \"select * from some_table where some_column = ?\"");
1397      ol("ps.setString(1, \"foo\");");
1398      ol("List list = fooMgr.<font color=blue>getUsing</font>(con, <font color=blue>ps</font>);");
1399      ol("for (int n = 0; n < list.size(); n++) {");
1400      ol("  sometable t = (sometable) list.get(n);");
1401      ol("  //do something");
1402      ol("  }");
1403      ol("</pre>");
1404      ol("</blockquote>");
1405      ol("The effect of the above is <u>equivalent</u> to the following (larger) block ");
1406      ol("of code:");
1407      ol("<blockquote>");
1408      ol("<pre>");
1409      ol("PreparedStatement <font color=blue>ps</font> = con.prepareStatement(");
1410      ol("  \"select * from sometable where some_column = ?\"");
1411      ol("  );");
1412      ol("ps.setString(1, \"foo\");");
1413      ol("ResultSet rs = <font color=blue>ps.executeQuery()</font>;");
1414      ol("List list = new ArrayList();");
1415      ol("while (rs.next()) {");
1416      ol("  list.add(sometableMgr.<font color=blue>getFromRS(rs)</font>);");
1417      ol("  }");
1418      ol("");
1419      ol("for