View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2000 William Chesters
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     William Chesters <williamc AT paneris.org>
42   *     http://paneris.org/~williamc
43   *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44   */
45  
46  package org.melati.poem.prepro;
47  
48  import java.util.ArrayList;
49  import java.util.Enumeration;
50  import java.util.Vector;
51  import java.util.Hashtable;
52  import java.util.Arrays;
53  import java.io.StreamTokenizer;
54  import java.io.Writer;
55  import java.io.IOException;
56  
57  /**
58   * A Table Definition holding information from a DSD.
59   * 
60   * 
61   * The SQL table naming convention is enforced here:
62   * the name given in the DSD is forced to lowercase. 
63   * 
64   *  This could be changed to enable mixed case names in 
65   *  jdbc names by simply not forcing to lowercase, 
66   *  but the JavaName has first letter capitalised. 
67   *  
68   *  The way around this is to have a separate jdbc name, 
69   *  but this would be a lot of redundancy for a small use case.
70   *  
71   *  The use case is actually broken: MySQL allows significant 
72   *  table name case, but only on operating systems where 
73   *  file name case is significant, and the manual advises against.
74   *  
75   *  The solution is to add the following to /etc/mysqld/my.cnf
76   *  
77   *  [mysqld]
78   *  lower_case_table_names=1  
79   * 
80   */
81  public class TableDef {
82  
83    DSD dsd;
84    /** Mixed case name. */
85    final String nameFromDsd;
86    final String capitalisedName;
87    /** Lowercase name. */
88    final String name;
89    String displayName;
90    String description;
91    String category;
92    String superclass;
93    int displayOrder;
94    boolean seqCached;
95    int cacheSize = CacheSizeTableQualifier.DEFAULT;
96    private Vector<FieldDef> fields = new Vector<FieldDef>();
97    boolean isAbstract;
98    boolean definesColumns;
99    TableNamingInfo tableNamingInfo = null;
100 
101   int nextFieldDisplayOrder = 0;
102   // Note we have to store the imports and process them at
103   // the end to avoid referring to a table that has yet to be processed.
104   // There is a similar problem with ReferenceFieldDef, really we need two passes
105   private final Hashtable<String, String> imports = new Hashtable<String, String>();
106   private final Vector<String> tableBaseImports = new Vector<String>();
107   private final Vector<String> persistentBaseImports = new Vector<String>();
108 
109   /**
110    * Constructor.
111    *
112    * @param dsd
113    *        the {@link DSD} this is a member of
114    * @param tokens
115    *        a stream of tokens
116    * @param displayOrder
117    *        the ordering within the DSD
118    * @param isAbstract
119    *        whether this is an abstract table
120    * @param nameStore
121    *        where to put our names
122    * @throws ParsingDSDException
123    *         if an unexpected token is encountered
124    * @throws IllegalityException
125    *         if a semantic incoherence is detected
126    * @throws IOException
127    *         if a problem with the file system is encountered
128    */
129   public TableDef(DSD dsd, StreamTokenizer tokens, int displayOrder,
130                   boolean isAbstract, TableNamingStore nameStore)
131       throws ParsingDSDException,
132       IOException,
133       IllegalityException {
134     this.dsd = dsd;
135     this.displayOrder = displayOrder;
136     this.isAbstract = isAbstract;
137     if (tokens.ttype != StreamTokenizer.TT_WORD)
138       throw new ParsingDSDException("<table name>", tokens);
139     nameFromDsd = tokens.sval;
140     name = nameFromDsd.toLowerCase();
141     capitalisedName = nameFromDsd.substring(0,1).toUpperCase() + nameFromDsd.substring(1);
142 
143     if (tokens.nextToken() == StreamTokenizer.TT_WORD) {
144       if (!tokens.sval.equals("extends"))
145         throw new ParsingDSDException("{", tokens);
146       tokens.wordChars('.', '.');
147       try {
148         if (tokens.nextToken() != StreamTokenizer.TT_WORD)
149           throw new ParsingDSDException("<class name>", tokens);
150       } finally {
151         tokens.ordinaryChar('.');
152       }
153       superclass = tokens.sval;
154     } else
155       tokens.pushBack();
156 
157     tableNamingInfo = nameStore.add(dsd, dsd.packageName, nameFromDsd, superclass);
158 
159     while (tokens.nextToken() == '(') {
160       tokens.nextToken();
161       TableQualifier.from(tokens).apply(this);
162       DSD.expect(tokens, ')');
163     }
164 
165     DSD.expect(tokens, '{');
166     while (tokens.nextToken() != '}')
167       fields.addElement(FieldDef.from(this, tokens, nextFieldDisplayOrder++));
168 
169     tokens.nextToken();
170 
171   }
172 
173   /**
174    * @param importName name of import
175    * @param destination "persistent", "table" or "both"
176    */
177   void addImport(String importName, String destination) {
178     if (!destination.equals("table") && !destination.equals("persistent")
179         && !destination.equals("both"))
180       throw new RuntimeException(
181                                  "Destination other than 'table', 'persistent' or 'both' used:"
182                                      + destination);
183 
184     String existing = null;
185     existing = imports.put(importName, destination);
186     if (existing != null && existing != destination)
187       imports.put(importName, "both");
188   }
189 
190   private final TableDef this_ = this;
191 
192   /**
193    * @param w
194    *        DatabaseBase
195    * @throws IOException
196    *         if a problem with the file system is encountered
197    */
198   public void generateTableDeclarationJava(Writer w)
199       throws IOException {
200     if (!isAbstract) {
201       w.write("  private " + tableNamingInfo.tableMainClassShortName() +
202           "<" + tableNamingInfo.mainClassShortName() + ">" +
203           " tab_" + name + " = null;\n");
204       /*
205       w.write("  private " + tableNamingInfo.tableMainClassRootReturnClass() +
206           "<" + tableNamingInfo.mainClassRootReturnClass() + ">" +
207           " tab_" + name + " = null;\n");
208    */
209     }
210   }
211 
212   /**
213    * @param w
214    *        DatabaseBase
215    * @throws IOException
216    *         if a problem with the file system is encountered
217    */
218   public void generateTableDefinitionJava(Writer w)
219       throws IOException {
220     if (!isAbstract)
221       w.write("    redefineTable(tab_" + name + " = " + "new "
222           + tableNamingInfo.tableMainClassUnambiguous() 
223           + ( 
224               (tableNamingInfo.tableMainClassRootReturnClass().equals(tableNamingInfo.tableMainClassUnambiguous()) 
225                   ?
226                   "<"+ tableNamingInfo.mainClassUnambiguous() +">" 
227                   : "")
228             )
229           + "(this, \"" + nameFromDsd + "\", "
230           + "DefinitionSource.dsd));\n");
231   }
232 
233   /**
234    * @param w
235    *        DatabaseBase
236    * @throws IOException
237    *         if a problem with the file system is encountered
238    */
239   public void generateTableAccessorJava(Writer w)
240       throws IOException {
241     if (isAbstract) 
242       return;
243 
244     generateTableAccessorDeclaration(w, false);    
245     w.write(" {\n" + "    return ");
246     // This cast is not actually required as ours is a subclass anyway
247     // but with generics we need to :(
248     if (!tableNamingInfo.tableMainClassRootReturnClass().equals(tableNamingInfo.tableMainClassUnambiguous()))
249       w.write("(" + tableNamingInfo.tableMainClassRootReturnClass() + ")");
250     w.write("tab_" + name + ";\n  }\n");
251     
252     if (tableNamingInfo.hidesOther) {
253       generateSubclassedTableAccessorDeclaration(w, false);    
254       w.write(" {\n" + "    return ");
255       w.write("tab_" + name + ";\n  }\n");      
256     }
257 
258   }
259 
260 
261   /**
262    * @param w
263    *        DatabaseTablesBase
264    * @throws IOException
265    *         if a problem with the file system is encountered
266    */
267   public void generateTableAccessorDefnJava(Writer w)
268       throws IOException {
269     if (isAbstract) 
270       return;
271 
272     generateTableAccessorDeclaration(w, true);    
273     w.write(";\n");
274     
275     if (tableNamingInfo.hidesOther) {
276       generateSubclassedTableAccessorDeclaration(w, true);    
277       w.write(";\n");
278     }
279 
280   }
281 
282   private void generateTableAccessorDeclaration(Writer w, boolean inInterface) throws IOException {
283     w.write("\n /**\n"
284           + "  * Retrieves the " + tableNamingInfo.tableMainClassShortName() + " table.\n"
285           + "  *\n");
286     if (!tableNamingInfo.tableMainClassRootReturnClass().equals(tableNamingInfo.tableMainClassUnambiguous()))
287       w.write("  * Deprecated: use get" + tableNamingInfo.projectName + tableNamingInfo.tableMainClassShortName() + "\n");
288     w.write("  * See org.melati.poem.prepro.TableDef#generateTableAccessorJava \n"
289           + "  * @return the " + tableNamingInfo.tableMainClassRootReturnClass() + " from this database\n" + "  */\n");
290     if (!inInterface)
291      if (!tableNamingInfo.tableMainClassRootReturnClass().equals(tableNamingInfo.tableMainClassUnambiguous()))
292         w.write("  @SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n");
293     w.write("  public " + tableNamingInfo.tableMainClassRootReturnClass() + 
294         "<" + tableNamingInfo.mainClassRootReturnClass() + "> get" + tableNamingInfo.tableMainClassShortName() + "()");
295   }
296   /** Disambiguate a table with the same name as an inherited one eg User */
297   private void generateSubclassedTableAccessorDeclaration(Writer w, boolean inInterface) throws IOException {
298     w.write("\n /**\n"
299         + "  * Retrieves our (" + tableNamingInfo.projectName + ") " + tableNamingInfo.tableMainClassShortName() + " table.\n"
300         + "  *\n"
301         + "  * See org.melati.poem.prepro.TableDef#generateSubclassedTableAccessorDeclaration \n"
302         + "  * @return the " + tableNamingInfo.tableMainClassRootReturnClass() + " from this database\n" + "  */\n");
303     w.write("  public " + tableNamingInfo.tableMainClassShortName() 
304         + "<" + tableNamingInfo.mainClassShortName() + "> " 
305         + tableNamingInfo.leafTableAccessorName() + "()");
306   }
307 
308   /**
309    * @param w
310    *        Persistent Base writer
311    * @throws IOException
312    *         if a problem with the file system is encountered
313    */
314   public void generatePersistentBaseJava(Writer w)
315       throws IOException {
316     for (Enumeration<String> e = persistentBaseImports.elements(); 
317          e.hasMoreElements();) {
318       String importedClassName = e.nextElement();
319       TableNamingInfo tni = dsd.tableNamingStore.tableInfoByTableOrPersistentFQName.get(importedClassName);
320       if (tableNamingInfo.extended != null) { 
321         if (tni != null)
322           if (tni.equals(tableNamingInfo)) 
323             w.write("// ours\n//import " + importedClassName + ";\n");
324           else 
325             if (tni.equals(tableNamingInfo.extended))
326               if (tableNamingInfo.extended.extended != null)
327                 w.write("// extends extended\n//import " + importedClassName + ";\n");
328               else
329                 w.write("// base extension\nimport " + importedClassName + ";\n");
330             else
331               w.write("import " + importedClassName + ";\n");              
332         else 
333           w.write("import " + importedClassName + ";\n");                 
334       } else
335         w.write("import " + importedClassName + ";\n");
336     }
337     w.write("\n");
338 
339     // if we subclass a table with the same name we need to cast the table to
340     // have the same return type as the root superclass
341     String rootReturnClass = tableNamingInfo.tableMainClassRootReturnClass();
342 
343     w.write("\n" + "/**\n"
344         + " * Melati POEM generated abstract base class for a "
345         + "<code>Persistent</code> \n" + 
346         " * <code>" + nameFromDsd + "</code> Object.\n" + " *\n" + 
347         " * See org.melati.poem.prepro.TableDef#generatePersistentBaseJava \n" +
348         " */\n");
349     w.write("public abstract class " + tableNamingInfo.baseClassUnambiguous()
350         + " extends " + tableNamingInfo.superclassMainUnambiguous() + " {\n" + "\n");
351 
352     w.write("\n /**\n" + 
353             "  * Retrieves the Database object.\n" + "  * \n" + 
354             "  * See org.melati.poem.prepro.TableDef#generatePersistentBaseJava \n" +
355             "  * @return the database\n" + "  */\n");
356     w.write("  public " + dsd.databaseTablesClassName + " get" + dsd.databaseTablesClassName + "() {\n" 
357         + "    return (" + dsd.databaseTablesClassName + ")getDatabase();\n" 
358         + "  }\n" + "\n");
359 
360     w.write("\n /**\n" 
361         + "  * Retrieves the  <code>" + tableNamingInfo.tableMainClassShortName() + "</code> table \n"
362         + "  * which this <code>Persistent</code> is from.\n" + "  * \n"
363         + "  * See org.melati.poem.prepro.TableDef#generatePersistentBaseJava \n"
364         + "  * @return the " + rootReturnClass + "\n" 
365         + "  */\n");
366     w.write("  @SuppressWarnings(\"unchecked\")\n");
367     w.write("  public " + rootReturnClass + "<"+tableNamingInfo.mainClassRootReturnClass()+"> "
368         + tableNamingInfo.rootTableAccessorName() + "() {\n" 
369         + "    return ("
370         + rootReturnClass + "<"+tableNamingInfo.mainClassRootReturnClass()+">)getTable();\n" 
371         + "  }\n\n");
372       
373     if (!fields.elements().hasMoreElements()) {
374       w.write("  // There are no Fields in this table, only in its ancestors \n");
375     } else {
376       w.write("  @SuppressWarnings(\"unchecked\")\n");
377       w.write("  private " + tableNamingInfo.tableMainClassUnambiguous() +"<"+tableNamingInfo.mainClassUnambiguous() + "> _" + tableNamingInfo.rootTableAccessorName() + "() {\n" 
378           + "    return ("
379           + tableNamingInfo.tableMainClassUnambiguous() + "<"+tableNamingInfo.mainClassUnambiguous()+">)getTable();\n" 
380           + "  }\n\n");
381 
382       w.write("  // Fields in this table \n");
383       for (Enumeration<FieldDef> f = fields.elements(); f.hasMoreElements();) {
384         FieldDef fd = f.nextElement();
385         w.write(" /**\n");
386         w.write(DSD.javadocFormat(2, 1,
387             ((fd.displayName != null) ? fd.displayName : fd.name)
388                 + ((fd.description != null) ? " - " + fd.description : "")));
389         w.write("  */\n");
390         w.write("  protected ");
391         fd.generateJavaDeclaration(w);
392 
393         w.write(";\n");
394       }
395 
396       for (Enumeration<FieldDef> f = fields.elements(); f.hasMoreElements();) {
397         FieldDef field = f.nextElement();
398         w.write('\n');
399         field.generateBaseMethods(w);
400         w.write('\n');
401         field.generateFieldCreator(w);
402       }
403     }
404 
405     // for references to this table in tableDefs
406     //  write getReferencesByColumname
407     for (TableDef t : dsd.tablesInDatabase) { 
408       if (!t.isAbstract && t.superclass == null) { //TODO handle abstract and extended classes
409         for (FieldDef f : t.fields) { 
410           if (f instanceof ReferenceFieldDef) { 
411             ReferenceFieldDef rfd = (ReferenceFieldDef) f;
412             if (rfd.getTargetTableNamingInfo() != null && rfd.getTargetTableNamingInfo().mainClassFQName().equals(tableNamingInfo.mainClassFQName())) {
413               w.write('\n');          
414               w.write("  private CachedSelection<" + rfd.shortestUnambiguousClassname +"> "+rfd.name+rfd.shortestUnambiguousClassname + "s = null;\n");
415               w.write("  /** References to this "+tableNamingInfo.mainClassShortName()+" in the " + rfd.shortestUnambiguousClassname+" table via its "+ rfd.name+" field.*/\n");
416               w.write("  @SuppressWarnings(\"unchecked\")\n");
417               w.write("  public Enumeration<" + rfd.shortestUnambiguousClassname +"> get" + StringUtils.capitalised(rfd.name)+rfd.shortestUnambiguousClassname + "s() {\n");
418               w.write("    if (getTroid() == null)\n");
419               w.write("      return new EmptyEnumeration<" + rfd.shortestUnambiguousClassname +">();\n");
420               w.write("    else {\n");
421               w.write("      if (" + rfd.name+rfd.shortestUnambiguousClassname + "s == null)\n");
422               w.write("        " + rfd.name+rfd.shortestUnambiguousClassname + "s =\n");
423               w.write("          get" + dsd.databaseTablesClassName + "().get"+rfd.shortestUnambiguousClassname+"Table().get"+StringUtils.capitalised(rfd.name)+"Column().cachedSelectionWhereEq(getTroid());\n");
424               w.write("      return " + rfd.name+rfd.shortestUnambiguousClassname + "s.objects();\n");
425               w.write("    }\n");
426               w.write("  }\n");
427               w.write("\n");
428               w.write("\n");
429               w.write("  /** References to this "+tableNamingInfo.mainClassShortName()+" in the " + rfd.shortestUnambiguousClassname+" table via its "+ rfd.name+" field, as a List.*/\n");
430               w.write("  public List<" + StringUtils.capitalised(rfd.shortestUnambiguousClassname) +"> get" + StringUtils.capitalised(rfd.name)+rfd.shortestUnambiguousClassname + "List() {\n");
431               w.write("    return Collections.list(get" + StringUtils.capitalised(rfd.name)+rfd.shortestUnambiguousClassname + "s());\n");
432               w.write("  }\n");
433               w.write("\n");
434               w.write("\n");
435             }
436           }
437         }
438       }
439     }
440     w.write('\n');
441     
442     w.write("}\n");
443   }
444 
445   /**
446    * @param w
447    *        Persistent writer
448    * @throws IOException
449    *         if a problem with the file system is encountered
450    */
451   public void generatePersistentJava(Writer w)
452       throws IOException {
453 
454     w.write("import " + dsd.packageName + ".generated."
455         + tableNamingInfo.baseClassShortName() + ";\n");
456     w.write("\n/**\n"
457         + " * Melati POEM generated, programmer modifiable stub \n"
458         + " * for a <code>Persistent</code> <code>"
459         + tableNamingInfo.mainClassShortName() + "</code> object.\n");
460     w.write(" * \n"
461         + (description != null ? " * <p> \n"
462             + " * Description: \n"
463             + DSD.javadocFormat(1, 3, (description + ((description
464                 .lastIndexOf(".") != description.length() - 1) ? "." : "")))
465             + " * </p>\n" : ""));
466     w.write(fieldSummaryTable());
467     w.write(" * \n" + " * See org.melati.poem.prepro.TableDef"
468         + "#generatePersistentJava \n" + " */\n");
469     w.write("public class " + tableNamingInfo.mainClassShortName() + " extends "
470         + tableNamingInfo.baseClassShortName() + " {\n");
471 
472     w.write("\n /**\n"
473             + "  * Constructor \n"
474             + "  * for a <code>Persistent</code> <code>"
475             + tableNamingInfo.mainClassShortName()
476             + "</code> object.\n"
477             + (description != null ? ("  * <p>\n"
478                 + "  * Description: \n"
479                 + DSD
480                     .javadocFormat(2, 3, (description + ((description
481                         .lastIndexOf(".") != description.length() - 1) ? "."
482                         : ""))) + "  * </p>\n") : "") + "  * \n"
483             + "  * See org.melati.poem.prepro.TableDef"
484             + "#generatePersistentJava \n" + "  */\n");
485 
486     w.write("  public " + tableNamingInfo.mainClassShortName() + "() { \n"
487             + "    super();\n"
488             + "}\n" + "\n"
489             + "  // programmer's domain-specific code here\n" + "}\n");
490   }
491 
492   /**
493    * @param w
494    *        TableBase
495    * @throws IOException
496    *         if a problem with the file system is encountered
497    */
498   public void generateTableBaseJava(Writer w)
499       throws IOException {
500 
501     for (Enumeration<String> e = tableBaseImports.elements(); e.hasMoreElements();) {
502       String packageName = e.nextElement();
503       if (ambiguous(packageName)) 
504         w.write("// Extended table \nimport " + packageName + ";\n");
505       else
506         w.write("import " + packageName + ";\n");
507     }
508 
509     w.write("\n");
510     w.write("\n" 
511         + "/**\n" 
512         + " * Melati POEM generated base class for " + "<code>Table</code> <code>" + nameFromDsd + "</code>.\n");
513     w.write(" *\n" 
514         + " * See org.melati.poem.prepro.TableDef"
515         + "#generateTableBaseJava \n" + " */\n\n");
516     w.write("public class " + tableNamingInfo.tableBaseClassShortName() + "<T extends "+tableNamingInfo.mainClassShortName()+"> extends "
517         + tableNamingInfo.superclassTableShortName() + "<T> {\n" 
518         + "\n");
519 
520     for (Enumeration<FieldDef> f = fields.elements(); f.hasMoreElements();) {
521       w.write("  private ");
522       (f.nextElement()).generateColDecl(w);
523       w.write(" = null;\n");
524     }
525 
526     w.write("\n /**\n" + "  * Constructor. \n" 
527         + "  * \n" 
528         + "  * See org.melati.poem.prepro.TableDef" + "#generateTableBaseJava \n"
529         + "  * @param database          the POEM database we are using\n"
530         + "  * @param name              the name of this <code>Table</code>\n"
531         + "  * @param definitionSource  which definition is being used\n"
532         + "  * @throws PoemException    if anything goes wrong\n" + "  */\n");
533     w.write("\n" + "  public " + tableNamingInfo.tableBaseClassShortName() + "(\n"
534         + "      Database database, String name,\n"
535         + "      DefinitionSource definitionSource)"
536         + " throws PoemException {\n"
537         + "    super(database, name, definitionSource);\n" + "  }\n" + "\n");
538 
539     
540     //w.write("\n /**\n" + "  * Constructor.\n" 
541     //    + "  *\n" 
542     //    + "  * See org.melati.poem.prepro.TableDef" + "#generateTableBaseJava \n"
543     //    + "  * @param database          the POEM database we are using\n"
544     //    + "  * @param name              the name of this <code>Table</code>\n"
545     //    + "  * @throws PoemException    if anything goes wrong\n" 
546     //    + "  */\n");
547     //w.write("  public " + naming.tableBaseClassShortName() + "(\n"
548     //    + "      Database database, String name)" 
549     //    + " throws PoemException {\n"
550     //    + "    this(database, name, DefinitionSource.dsd);\n" + "  }\n" 
551     //    + "\n");
552     
553     w.write("\n /**\n" 
554         + "  * Get the database tables.\n" + "  *\n"
555         + "  * See org.melati.poem.prepro.TableDef#generateTableBaseJava \n"
556         + "  * @return the database tables\n"
557         + "  */\n");
558     w.write("  public " + dsd.databaseTablesClassName + " get"+ dsd.databaseTablesClassName + "() {\n" + 
559         "    return (" + dsd.databaseTablesClassName + ")getDatabase();\n" + 
560         "  }\n" + 
561         "\n"); 
562 
563     w.write("\n /**\n" 
564         + "  * Initialise this table by defining its columns.\n" 
565         + "  *\n"
566         + "  * See org.melati.poem.prepro.TableDef#generateTableBaseJava \n"
567         + "  */\n");
568     w.write(
569         "  public void init() throws PoemException {\n" + 
570         "    super.init();\n");
571 
572     for (Enumeration<FieldDef> fs = fields.elements(); fs.hasMoreElements();) {
573       (fs.nextElement()).generateColDefinition(w);
574       if (fs.hasMoreElements())
575         w.write('\n');
576     }
577 
578     w.write("  }\n" + "\n");
579 
580     for (Enumeration<FieldDef> fs = fields.elements(); fs.hasMoreElements();) {
581       (fs.nextElement()).generateColAccessor(w);
582       w.write('\n');
583     }
584 
585     // if we subclass a table with the same name we need to cast the table to
586     // have the same return type as the root superclass
587     String requiredReturnClass = tableNamingInfo.mainClassRootReturnClass();
588 
589     w.write("\n /**\n" + "  * Retrieve the <code>"
590         + tableNamingInfo.mainClassShortName() + "</code> as a <code>"
591         + requiredReturnClass + "</code>.\n" 
592         + "  *\n" 
593         + "  * See org.melati.poem.prepro.TableDef" + "#generateTableBaseJava \n"
594         + "  * @param troid a Table Row Object ID\n"
595         + "  * @return the <code>Persistent</code> identified "
596         + "by the <code>troid</code>\n" + "  */\n");
597     w.write("  public " + requiredReturnClass + " get"
598         + tableNamingInfo.mainClassShortName() + "Object(" + "Integer troid) {\n"
599         + "    return (" + requiredReturnClass + ")getObject(troid);\n"
600         + "  }\n" + "\n");
601 
602     w.write("\n /**\n" + "  * Retrieve the <code>"
603         + tableNamingInfo.mainClassShortName() + "</code> \n" 
604         + "  * as a <code>" + requiredReturnClass + "</code>.\n" 
605         + "  *\n" 
606         + "  * See org.melati.poem.prepro.TableDef" + "#generateTableBaseJava \n"
607         + "  * @param troid a Table Row Object ID\n"
608         + "  * @return the <code>Persistent</code> identified " + "  */\n");
609     w.write("  public " + requiredReturnClass + " get"
610         + tableNamingInfo.mainClassShortName() + "Object(" + "int troid) {\n"
611         + "    return (" + requiredReturnClass + ")getObject(troid);\n"
612         + "  }\n");
613 
614     if (!isAbstract)
615       w.write("\n" + "  protected JdbcPersistent _newPersistent() {\n"
616           + "    return new " + tableNamingInfo.mainClassUnambiguous() + "();\n" + "  }"
617           + "\n");
618     
619     if (displayName != null)
620       w.write("  public String defaultDisplayName() {\n" + "    return "
621           + StringUtils.quoted(displayName, '"') + ";\n" + "  }\n" + "\n");
622 
623     if (description != null)
624       w.write("  public String defaultDescription() {\n" + "    return "
625           + StringUtils.quoted(description, '"') + ";\n" + "  }\n" + "\n");
626 
627     if (seqCached)
628       w.write("  public boolean defaultRememberAllTroids() {\n"
629           + "    return true;\n" + "  }\n" + "\n");
630 
631     if (cacheSize != CacheSizeTableQualifier.DEFAULT)
632       w.write("  public Integer defaultCacheLimit() {\n"
633           + "    return new Integer("
634           + (cacheSize == CacheSizeTableQualifier.UNLIMITED ? "999999999" : ""
635               + cacheSize) + ");\n" + "  }\n" + "\n");
636 
637     if (category != null)
638       w.write("  public String defaultCategory() {\n" + "    return "
639           + StringUtils.quoted(category, '"') + ";\n" + "  }\n" + "\n");
640 
641     w.write("  public int defaultDisplayOrder() {\n" + "    return "
642         + displayOrder + ";\n" + "  }\n");
643 
644     FieldDef uniqueNonNullableField = null; 
645     ArrayList<FieldDef> requiredFields = new ArrayList<FieldDef>();
646     for (Enumeration<FieldDef> fs = fields.elements(); fs.hasMoreElements();) {
647       FieldDef f = fs.nextElement();
648       if (!f.isNullable() && f.isUnique() && !f.isTroidColumn() 
649           && uniqueNonNullableField == null){
650         uniqueNonNullableField = f;
651       }
652       if (!f.isNullable() && !f.isTroidColumn()){
653         requiredFields.add(f);
654       }
655     }
656     if (uniqueNonNullableField != null) { 
657       w.write("\n");
658       w.write("  /**\n");
659       w.write("   * @return a newly created or existing " 
660               + tableNamingInfo.mainClassShortName() + "\n");
661       w.write("   **/\n");
662       w.write("  public " + tableNamingInfo.mainClassShortName() + " ensure(");
663       boolean seenOne = false;
664       for (FieldDef f : requiredFields){
665         if (seenOne)
666           w.write(", ");
667         w.write(f.typeShortName);
668         w.write(" ");
669         w.write(f.name);
670         seenOne = true;
671       }
672       w.write(") {\n");
673       //Book p = (Book)getTitleColumn().firstWhereEq(title);
674       w.write("    " 
675           + tableNamingInfo.mainClassShortName() + " p = ("
676           + tableNamingInfo.mainClassShortName() +")get"
677           + uniqueNonNullableField.capitalisedName + "Column().firstWhereEq("
678           + uniqueNonNullableField.name + ");\n");
679       //    if (p == null) { 
680       //      p = (Book)newPersistent();
681       w.write("    if (p == null) {\n"
682             + "      p = (" + tableNamingInfo.mainClassShortName() + ")newPersistent();\n");
683       //      p.setTitle(title);
684       for (FieldDef f : requiredFields){
685         w.write("      p.set");
686         w.write(f.capitalisedName);
687         w.write("(");
688         w.write(f.name);
689         w.write(");\n");
690       }
691       w.write("    }\n");
692       //      return (Book)getTitleColumn().ensure(p);
693       w.write("    return (" + tableNamingInfo.mainClassShortName() 
694           + ")get" + uniqueNonNullableField.capitalisedName + "Column().ensure(p);\n");
695 
696       
697       w.write("  }\n");
698     }
699     
700   
701     w.write("}\n");
702   }
703 
704   private boolean ambiguous(String packageName) {
705     TableNamingInfo tni = dsd.tableNamingStore.tableInfoByTableOrPersistentFQName.get(packageName);
706     if (tni == null)
707       return false;
708     else if(tni.hidden || tni.hidesOther)
709       return true;
710     return false;
711   }
712 
713   /**
714    * @param w
715    *        Table
716    * @throws IOException
717    *         if a problem with the file system is encountered
718    */
719   public void generateTableJava(Writer w)
720       throws IOException {
721 
722     w.write("import " + tableNamingInfo.tableBaseClassFQName() + ";\n");
723     w.write("import org.melati.poem.DefinitionSource;\n");
724     w.write("import org.melati.poem.Database;\n");
725     w.write("import org.melati.poem.PoemException;\n");
726 
727     w.write("\n/**\n"
728         + " * Melati POEM generated, programmer modifiable stub \n"
729         + " * for a <code>"
730         + tableNamingInfo.tableMainClassShortName()
731         + "</code> object.\n"
732         + (description != null ? " * <p>\n"
733             + " * Description: \n"
734             + DSD.javadocFormat(1, 3, (description + ((description
735                 .lastIndexOf(".") != description.length() - 1) ? "." : "")))
736             + " * </p>\n" : "") + " *\n");
737     w.write(fieldSummaryTable());
738     w.write(" * \n" 
739         + " * See org.melati.poem.prepro.TableDef" + "#generateTableJava \n" + " */\n");
740     w.write("public class " + tableNamingInfo.tableMainClassShortName() + "<T extends "+tableNamingInfo.mainClassShortName()+"> extends "
741         + tableNamingInfo.tableBaseClassShortName() + "<"+tableNamingInfo.mainClassShortName()+"> {\n");
742     
743     Object o = new Object() {
744       public String toString() {
745         return "\n /**\n"
746             + "  * Constructor.\n"
747             + "  * \n"
748             + "  * See org.melati.poem.prepro.TableDef" + "#generateTableJava \n"
749             + "  * @param database          the POEM database we are using\n"
750             + "  * @param name              the name of this <code>Table</code>\n"
751             + "  * @param definitionSource  which definition is being used\n"
752             + "  * @throws PoemException    if anything goes wrong\n"
753             + "  */\n";
754       }
755     };
756     w.write(o.toString());
757     w.write("  public " + tableNamingInfo.tableMainClassShortName() + "(\n"
758         + "      Database database, String name,\n"
759         + "      DefinitionSource definitionSource)"
760         + " throws PoemException {\n"
761         + "    super(database, name, definitionSource);\n" + "  }\n" + "\n"
762         + "  // programmer's domain-specific code here\n" + "}\n");
763   }
764 
765   /**
766    * Generate the 4 files.
767    *
768    * @throws IOException
769    *         if a problem with the file system is encountered
770    * @throws IllegalityException
771    *         if a semantic incoherence is detected
772    */
773   public void generateJava()
774       throws IOException, IllegalityException {
775 
776     boolean hasDisplayLevel = false;
777     boolean hasSearchability = false;
778     
779     boolean needSelectionImports = false;
780     for (TableDef t : dsd.tablesInDatabase) { 
781       if (!t.isAbstract && t.superclass == null)
782       for (FieldDef f : t.fields) { 
783         if (f instanceof ReferenceFieldDef) { 
784           ReferenceFieldDef rfd = (ReferenceFieldDef) f;
785           if (!(rfd.getTargetTableNamingInfo() == null)  && 
786               rfd.getTargetTableNamingInfo().mainClassFQName().equals(tableNamingInfo.mainClassFQName())) {
787             needSelectionImports = true;
788             addImport(rfd.table.tableNamingInfo.mainClassFQName(), "persistent");
789           }
790         }
791       }
792     }
793     if (needSelectionImports) {
794       addImport("org.melati.poem.CachedSelection", "persistent");
795       addImport("org.melati.poem.util.EmptyEnumeration","persistent");
796       addImport("java.util.Enumeration","persistent");
797       addImport("java.util.List","persistent");
798       addImport("java.util.Collections","persistent");
799     }
800     
801     int fieldCount = 0;
802     for (Enumeration<FieldDef> e = fields.elements(); e.hasMoreElements();) {
803       fieldCount++;
804       FieldDef f = e.nextElement();
805       if (f.displayLevel != null)
806         hasDisplayLevel = true;
807       if (f.searchability != null)
808         hasSearchability = true;
809     }
810     if (fieldCount == 0 && !isAbstract && tableNamingInfo.superclass == null)
811       throw new NonAbstractEmptyTableException(name);
812 
813     if (!isAbstract)
814       addImport("org.melati.poem.JdbcPersistent", "table");
815     if (hasDisplayLevel)
816       addImport("org.melati.poem.DisplayLevel", "table");
817     if (hasSearchability)
818       addImport("org.melati.poem.Searchability", "table");
819     addImport(tableNamingInfo.objectFQName, "table");
820     if (definesColumns) {
821       addImport("org.melati.poem.Column", "both");
822       addImport("org.melati.poem.Field", "both");
823     }
824     if (tableNamingInfo.superclassMainUnambiguous().equals("JdbcPersistent")) {
825       addImport("org.melati.poem.JdbcPersistent", "persistent");
826     } else {
827       addImport(tableNamingInfo.superclassMainFQName(), "persistent");
828     }
829 
830     // we may not have any fields in an overridden class
831     // but we need the import for getTable
832     addImport(tableNamingInfo.tableMainClassFQName(), "persistent");
833     addImport(dsd.packageName + "." + dsd.databaseTablesClassName, "persistent");
834 
835     addImport("org.melati.poem.Database", "table");
836     addImport("org.melati.poem.DefinitionSource", "table");
837     addImport("org.melati.poem.PoemException", "table");
838 
839     if (!isAbstract && definesColumns)
840       addImport("org.melati.poem.Persistent", "table");
841 
842     if (tableNamingInfo.superclassTableUnambiguous().equals("Table")) {
843       addImport("org.melati.poem.Table", "table");
844     } else {
845       addImport(tableNamingInfo.superclassTableFQName(), "table");
846     }
847     addImport(dsd.packageName + "." + dsd.databaseTablesClassName, "table");
848     addImport(tableNamingInfo.mainClassFQName(), "persistent");
849 
850     // Sort out the imports
851     for (Enumeration<String> i = imports.keys(); i.hasMoreElements();) {
852       String fqKey;
853       String key = i.nextElement();
854       if (key.indexOf(".") == -1) {
855         TableNamingInfo targetTable = (TableNamingInfo) dsd.tableNamingStore.tableInfoByPersistentShortName
856             .get(key);
857         if (targetTable == null)
858           throw new RuntimeException("No TableNamingInfo for " + key + 
859                   ". This is probably a typo either in the table definition name or in a reference field.");
860         fqKey = targetTable.objectFQName;
861         String destination = imports.get(key);
862         imports.remove(key);
863         addImport(fqKey, destination);
864       }
865     }
866     for (Enumeration<String> i = imports.keys(); i.hasMoreElements();) {
867       String fqKey;
868       String key = i.nextElement();
869 
870       if (key.indexOf(".") == -1) {
871         TableNamingInfo targetTable = 
872             (TableNamingInfo)dsd.tableNamingStore.tableInfoByPersistentShortName.get(key);
873         fqKey = targetTable.objectFQName;
874       } else {
875         fqKey = key;
876       }
877       String destination = imports.get(key);
878       if (destination == "table") {
879         tableBaseImports.addElement(fqKey);
880       } else if (destination == "persistent") {
881         persistentBaseImports.addElement(fqKey);
882       } else {
883         tableBaseImports.addElement(fqKey);
884         persistentBaseImports.addElement(fqKey);
885       }
886     }
887     Object[] t = tableBaseImports.toArray();
888     Object[] p = persistentBaseImports.toArray();
889     Arrays.sort(t);
890     Arrays.sort(p);
891     tableBaseImports.removeAllElements();
892     persistentBaseImports.removeAllElements();
893     for (int i = 0; i < t.length; i++)
894       tableBaseImports.addElement((String)t[i]);
895     for (int i = 0; i < p.length; i++)
896       persistentBaseImports.addElement((String)p[i]);
897 
898     dsd.createJava(tableNamingInfo.baseClassShortName(), new Generator() {
899       public void process(Writer w)
900           throws IOException {
901         this_.generatePersistentBaseJava(w);
902       }
903     }, true);
904 
905     dsd.createJava(tableNamingInfo.mainClassShortName(), new Generator() {
906       public void process(Writer w)
907           throws IOException {
908         this_.generatePersistentJava(w);
909       }
910     }, false);
911 
912     dsd.createJava(tableNamingInfo.tableBaseClassShortName(), new Generator() {
913       public void process(Writer w)
914           throws IOException {
915         this_.generateTableBaseJava(w);
916       }
917     }, true);
918 
919     dsd.createJava(tableNamingInfo.tableMainClassShortName(), new Generator() {
920       public void process(Writer w)
921           throws IOException {
922         this_.generateTableJava(w);
923       }
924     }, false);
925   }
926 
927   String fieldSummaryTable() {
928     StringBuffer table = new StringBuffer();
929     table.append(" * \n" + " * <table> \n" + " * <caption>\n"
930         + " * Field summary for SQL table <code>" + nameFromDsd + "</code>\n"
931         + " * </caption>\n"
932         + " * <tr><th>Name</th><th>Type</th><th>Description</th></tr>\n");
933     for (Enumeration<FieldDef> f = fields.elements(); f.hasMoreElements();) {
934       FieldDef fd = f.nextElement();
935       table.append(DSD.javadocFormat(1, 1, "<tr><td> " + fd.name
936           + " </td><td> " + fd.typeShortName + " </td><td> "
937           + ((fd.description != null) ? fd.description : "&nbsp;")
938           + " </td></tr>"));
939     }
940     table.append(" * </table> \n");
941     return table.toString();
942   }
943 }