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 <column_list> from <table> order by <clause> 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