View Javadoc

1   package org.apache.torque.engine.database.model;
2   
3   /*
4    * Copyright 2001-2004 The Apache Software Foundation.
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.Hashtable;
21  import java.util.Iterator;
22  import java.util.List;
23  
24  import org.apache.commons.lang.StringUtils;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import org.apache.torque.engine.EngineException;
30  
31  import org.xml.sax.Attributes;
32  
33  /***
34   * Data about a table used in an application.
35   *
36   * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
37   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
38   * @author <a href="mailto:mpoeschl@marmot.at>Martin Poeschl</a>
39   * @author <a href="mailto:jmcnally@collab.net>John McNally</a>
40   * @author <a href="mailto:dlr@collab.net>Daniel Rall</a>
41   * @author <a href="mailto:byron_foster@byron_foster@yahoo.com>Byron Foster</a>
42   * @version $Id: Table.java,v 1.8 2004/08/23 00:30:10 seade Exp $
43   */
44  public class Table implements IDMethod
45  {
46      /*** Logging class from commons.logging */
47      private static Log log = LogFactory.getLog(Table.class);
48  
49      //private AttributeListImpl attributes;
50      private List columnList;
51      private List foreignKeys;
52      private List indices;
53      private List unices;
54      private List idMethodParameters;
55      private String name;
56      private String description;
57      private String javaName;
58      private String idMethod;
59      private String javaNamingMethod;
60      private Database tableParent;
61      private List referrers;
62      private List foreignTableNames;
63      private boolean containsForeignPK;
64      private Column inheritanceColumn;
65      private boolean skipSql;
66      private boolean abstractValue;
67      private String alias;
68      private String enterface;
69      private String pkg;
70      private String baseClass;
71      private String basePeer;
72      private Hashtable columnsByName;
73      private Hashtable columnsByJavaName;
74      private boolean needsTransactionInPostgres;
75      private boolean heavyIndexing;
76      private boolean forReferenceOnly;
77  
78  
79      /***
80       * Default Constructor
81       */
82      public Table()
83      {
84          this(null);
85      }
86  
87      /***
88       * Constructs a table object with a name
89       *
90       * @param name table name
91       */
92      public Table(String name)
93      {
94          this.name = name;
95          columnList = new ArrayList();
96          foreignKeys = new ArrayList(5);
97          indices = new ArrayList(5);
98          unices = new ArrayList(5);
99          columnsByName = new Hashtable();
100         columnsByJavaName = new Hashtable();
101     }
102 
103     /***
104      * Load the table object from an xml tag.
105      *
106      * @param attrib xml attributes
107      * @param defaultIdMethod defined at db level
108      */
109     public void loadFromXML(Attributes attrib, String defaultIdMethod)
110     {
111         name = attrib.getValue("name");
112         javaName = attrib.getValue("javaName");
113         idMethod = attrib.getValue("idMethod");
114 
115         // retrieves the method for converting from specified name to
116         // a java name.
117         javaNamingMethod = attrib.getValue("javaNamingMethod");
118         if (javaNamingMethod == null)
119         {
120             javaNamingMethod = getDatabase().getDefaultJavaNamingMethod();
121         }
122 
123         if ("null".equals(idMethod))
124         {
125             idMethod = defaultIdMethod;
126         }
127         skipSql = "true".equals(attrib.getValue("skipSql"));
128         // pkg = attrib.getValue("package");
129         abstractValue = "true".equals(attrib.getValue("abstract"));
130         baseClass = attrib.getValue("baseClass");
131         basePeer = attrib.getValue("basePeer");
132         alias = attrib.getValue("alias");
133         heavyIndexing = "true".equals(attrib.getValue("heavyIndexing"))
134                 || (!"false".equals(attrib.getValue("heavyIndexing"))
135                 && getDatabase().isHeavyIndexing());
136         description = attrib.getValue("description");
137         enterface = attrib.getValue("interface");
138     }
139 
140     /***
141      * <p>A hook for the SAX XML parser to call when this table has
142      * been fully loaded from the XML, and all nested elements have
143      * been processed.</p>
144      *
145      * <p>Performs heavy indexing and naming of elements which weren't
146      * provided with a name.</p>
147      */
148     public void doFinalInitialization()
149     {
150         // Heavy indexing must wait until after all columns composing
151         // a table's primary key have been parsed.
152         if (heavyIndexing)
153         {
154             doHeavyIndexing();
155         }
156 
157         // Name any indices which are missing a name using the
158         // appropriate algorithm.
159         doNaming();
160     }
161 
162     /***
163      * <p>Adds extra indices for multi-part primary key columns.</p>
164      *
165      * <p>For databases like MySQL, values in a where clause must
166      * match key part order from the left to right.  So, in the key
167      * definition <code>PRIMARY KEY (FOO_ID, BAR_ID)</code>,
168      * <code>FOO_ID</code> <i>must</i> be the first element used in
169      * the <code>where</code> clause of the SQL query used against
170      * this table for the primary key index to be used.  This feature
171      * could cause problems under MySQL with heavily indexed tables,
172      * as MySQL currently only supports 16 indices per table (i.e. it
173      * might cause too many indices to be created).</p>
174      *
175      * <p>See <a href="http://www.mysql.com/doc/E/X/EXPLAIN.html">the
176      * manual</a> for a better description of why heavy indexing is
177      * useful for quickly searchable database tables.</p>
178      */
179     private void doHeavyIndexing()
180     {
181         if (log.isDebugEnabled())
182         {
183             log.debug("doHeavyIndex() called on table " + name);
184         }
185 
186         List pk = getPrimaryKey();
187         int size = pk.size();
188 
189         try
190         {
191             // We start at an offset of 1 because the entire column
192             // list is generally implicitly indexed by the fact that
193             // it's a primary key.
194             for (int i = 1; i < size; i++)
195             {
196                 addIndex(new Index(this, pk.subList(i, size)));
197             }
198         }
199         catch (EngineException e)
200         {
201             log.error(e, e);
202         }
203     }
204 
205     /***
206      * Names composing objects which haven't yet been named.  This
207      * currently consists of foreign-key and index entities.
208      */
209     private void doNaming()
210     {
211         int i;
212         int size;
213         String name;
214 
215         // Assure names are unique across all databases.
216         try
217         {
218             for (i = 0, size = foreignKeys.size(); i < size; i++)
219             {
220                 ForeignKey fk = (ForeignKey) foreignKeys.get(i);
221                 name = fk.getName();
222                 if (StringUtils.isEmpty(name))
223                 {
224                     name = acquireConstraintName("FK", i + 1);
225                     fk.setName(name);
226                 }
227             }
228 
229             for (i = 0, size = indices.size(); i < size; i++)
230             {
231                 Index index = (Index) indices.get(i);
232                 name = index.getName();
233                 if (StringUtils.isEmpty(name))
234                 {
235                     name = acquireConstraintName("I", i + 1);
236                     index.setName(name);
237                 }
238             }
239 
240             for (i = 0, size = unices.size(); i < size; i++)
241             {
242                 Unique unique = (Unique) unices.get(i);
243                 name = unique.getName();
244                 if (StringUtils.isEmpty(name))
245                 {
246                     name = acquireConstraintName("U", i + 1);
247                     unique.setName(name);
248                 }
249             }
250         }
251         catch (EngineException nameAlreadyInUse)
252         {
253             log.error(nameAlreadyInUse, nameAlreadyInUse);
254         }
255     }
256 
257     /***
258      * Macro to a constraint name.
259      *
260      * @param nameType constraint type
261      * @param nbr unique number for this constraint type
262      * @return unique name for constraint
263      * @throws EngineException
264      */
265     private final String acquireConstraintName(String nameType, int nbr)
266             throws EngineException
267     {
268         List inputs = new ArrayList(4);
269         inputs.add(getDatabase());
270         inputs.add(getName());
271         inputs.add(nameType);
272         inputs.add(new Integer(nbr));
273         return NameFactory.generateName(NameFactory.CONSTRAINT_GENERATOR,
274                                         inputs);
275     }
276 
277     /***
278      * Gets the value of base class for classes produced from this table.
279      *
280      * @return The base class for classes produced from this table.
281      */
282     public String getBaseClass()
283     {
284         if (isAlias() && baseClass == null)
285         {
286             return alias;
287         }
288         else if (baseClass == null)
289         {
290             return getDatabase().getBaseClass();
291         }
292         else
293         {
294             return baseClass;
295         }
296     }
297 
298     /***
299      * Set the value of baseClass.
300      * @param v  Value to assign to baseClass.
301      */
302     public void setBaseClass(String v)
303     {
304         this.baseClass = v;
305     }
306 
307     /***
308      * Get the value of basePeer.
309      * @return value of basePeer.
310      */
311     public String getBasePeer()
312     {
313         if (isAlias() && basePeer == null)
314         {
315             return alias + "Peer";
316         }
317         else if (basePeer == null)
318         {
319             return getDatabase().getBasePeer();
320         }
321         else
322         {
323             return basePeer;
324         }
325     }
326 
327     /***
328      * Set the value of basePeer.
329      * @param v  Value to assign to basePeer.
330      */
331     public void setBasePeer(String v)
332     {
333         this.basePeer = v;
334     }
335 
336     /***
337      * A utility function to create a new column from attrib and add it to this
338      * table.
339      *
340      * @param attrib xml attributes for the column to add
341      * @return the added column
342      */
343     public Column addColumn(Attributes attrib)
344     {
345         Column col = new Column();
346         col.setTable(this);
347         col.loadFromXML(attrib);
348         addColumn(col);
349         return col;
350     }
351 
352     /***
353      * Adds a new column to the column list and set the
354      * parent table of the column to the current table
355      *
356      * @param col the column to add
357      */
358     public void addColumn(Column col)
359     {
360         col.setTable (this);
361         if (col.isInheritance())
362         {
363             inheritanceColumn = col;
364         }
365         columnList.add(col);
366         columnsByName.put(col.getName(), col);
367         columnsByJavaName.put(col.getJavaName(), col);
368         col.setPosition(columnList.size());
369         needsTransactionInPostgres |= col.requiresTransactionInPostgres();
370     }
371 
372     /***
373      * A utility function to create a new foreign key
374      * from attrib and add it to this table.
375      *
376      * @param attrib the xml attributes
377      * @return the created ForeignKey
378      */
379     public ForeignKey addForeignKey(Attributes attrib)
380     {
381         ForeignKey fk = new ForeignKey();
382         fk.loadFromXML(attrib);
383         addForeignKey(fk);
384         return fk;
385     }
386 
387     /***
388      * Gets the column that subclasses of the class representing this
389      * table can be produced from.
390      */
391     public Column getChildrenColumn()
392     {
393         return inheritanceColumn;
394     }
395 
396     /***
397      * Get the objects that can be created from this table.
398      */
399     public List getChildrenNames()
400     {
401         if (inheritanceColumn == null
402                 || !inheritanceColumn.isEnumeratedClasses())
403         {
404             return null;
405         }
406         List children = inheritanceColumn.getChildren();
407         List names = new ArrayList(children.size());
408         for (int i = 0; i < children.size(); i++)
409         {
410             names.add(((Inheritance) children.get(i)).getClassName());
411         }
412         return names;
413     }
414 
415     /***
416      * Adds the foreign key from another table that refers to this table.
417      *
418      * @param fk A foreign key refering to this table
419      */
420     public void addReferrer(ForeignKey fk)
421     {
422         if (referrers == null)
423         {
424             referrers = new ArrayList(5);
425         }
426         referrers.add(fk);
427     }
428 
429     /***
430      * Get list of references to this table.
431      *
432      * @return A list of references to this table
433      */
434     public List getReferrers()
435     {
436         return referrers;
437     }
438 
439     /***
440      * Set whether this table contains a foreign PK
441      *
442      * @param b
443      */
444     public void setContainsForeignPK(boolean b)
445     {
446         containsForeignPK = b;
447     }
448 
449     /***
450      * Determine if this table contains a foreign PK
451      */
452     public boolean getContainsForeignPK()
453     {
454         return containsForeignPK;
455     }
456 
457     /***
458      * A list of tables referenced by foreign keys in this table
459      *
460      * @return A list of tables
461      */
462     public List getForeignTableNames()
463     {
464         if (foreignTableNames == null)
465         {
466             foreignTableNames = new ArrayList(1);
467         }
468         return foreignTableNames;
469     }
470 
471     /***
472      * Adds a new FK to the FK list and set the
473      * parent table of the column to the current table
474      *
475      * @param fk A foreign key
476      */
477     public void addForeignKey(ForeignKey fk)
478     {
479         fk.setTable (this);
480         foreignKeys.add(fk);
481 
482         if (foreignTableNames == null)
483         {
484             foreignTableNames = new ArrayList(5);
485         }
486         if (!foreignTableNames.contains(fk.getForeignTableName()))
487         {
488             foreignTableNames.add(fk.getForeignTableName());
489         }
490     }
491 
492     /***
493      * Return true if the column requires a transaction in Postgres
494      */
495     public boolean requiresTransactionInPostgres()
496     {
497         return needsTransactionInPostgres;
498     }
499 
500     /***
501      * A utility function to create a new id method parameter
502      * from attrib and add it to this table.
503      */
504     public IdMethodParameter addIdMethodParameter(Attributes attrib)
505     {
506         IdMethodParameter imp = new IdMethodParameter();
507         imp.loadFromXML(attrib);
508         addIdMethodParameter(imp);
509         return imp;
510     }
511 
512 
513     /***
514      * Adds a new ID method parameter to the list and sets the parent
515      * table of the column associated with the supplied parameter to this table.
516      *
517      * @param imp The column to add as an ID method parameter.
518      */
519     public void addIdMethodParameter(IdMethodParameter imp)
520     {
521         imp.setTable(this);
522         if (idMethodParameters == null)
523         {
524             idMethodParameters = new ArrayList(2);
525         }
526         idMethodParameters.add(imp);
527     }
528 
529     /***
530      * Adds a new index to the index list and set the
531      * parent table of the column to the current table
532      */
533     public void addIndex(Index index)
534     {
535         index.setTable (this);
536         indices.add(index);
537     }
538 
539     /***
540      * A utility function to create a new index
541      * from attrib and add it to this table.
542      */
543     public Index addIndex(Attributes attrib)
544     {
545         Index index = new Index();
546         index.loadFromXML(attrib);
547         addIndex(index);
548         return index;
549     }
550 
551     /***
552      * Adds a new Unique to the Unique list and set the
553      * parent table of the column to the current table
554      */
555     public void addUnique(Unique unique)
556     {
557         unique.setTable(this);
558         unices.add(unique);
559     }
560 
561     /***
562      * A utility function to create a new Unique
563      * from attrib and add it to this table.
564      *
565      * @param attrib the xml attributes
566      */
567     public Unique addUnique(Attributes attrib)
568     {
569         Unique unique = new Unique();
570         unique.loadFromXML(attrib);
571         addUnique(unique);
572         return unique;
573     }
574 
575     /***
576      * Get the name of the Table
577      */
578     public String getName()
579     {
580         return name;
581     }
582 
583     /***
584      * Set the name of the Table
585      */
586     public void setName(String newName)
587     {
588         name = newName;
589     }
590 
591     /***
592      * Get the description for the Table
593      */
594     public String getDescription()
595     {
596         return description;
597     }
598 
599     /***
600      * Set the description for the Table
601      *
602      * @param newDescription description for the Table
603      */
604     public void setDescription(String newDescription)
605     {
606         description = newDescription;
607     }
608 
609     /***
610      * Get name to use in Java sources
611      */
612     public String getJavaName()
613     {
614         if (javaName == null)
615         {
616             List inputs = new ArrayList(2);
617             inputs.add(name);
618             inputs.add(javaNamingMethod);
619             try
620             {
621                 javaName = NameFactory.generateName(NameFactory.JAVA_GENERATOR,
622                                                     inputs);
623             }
624             catch (EngineException e)
625             {
626                 log.error(e, e);
627             }
628         }
629         return javaName;
630     }
631 
632     /***
633      * Set name to use in Java sources
634      */
635     public void setJavaName(String javaName)
636     {
637         this.javaName = javaName;
638     }
639 
640     /***
641      * Get the method for generating pk's
642      */
643     public String getIdMethod()
644     {
645         if (idMethod == null)
646         {
647             return IDMethod.NO_ID_METHOD;
648         }
649         else
650         {
651             return idMethod;
652         }
653     }
654 
655     /***
656      * Set the method for generating pk's
657      */
658     public void setIdMethod(String idMethod)
659     {
660         this.idMethod = idMethod;
661     }
662 
663     /***
664      * Skip generating sql for this table (in the event it should
665      * not be created from scratch).
666      * @return value of skipSql.
667      */
668     public boolean isSkipSql()
669     {
670         return (skipSql || isAlias() || isForReferenceOnly());
671     }
672 
673     /***
674      * Set whether this table should have its creation sql generated.
675      * @param v  Value to assign to skipSql.
676      */
677     public void setSkipSql(boolean  v)
678     {
679         this.skipSql = v;
680     }
681 
682     /***
683      * JavaName of om object this entry references.
684      * @return value of external.
685      */
686     public String getAlias()
687     {
688         return alias;
689     }
690 
691     /***
692      * Is this table specified in the schema or is there just
693      * a foreign key reference to it.
694      * @return value of external.
695      */
696     public boolean isAlias()
697     {
698         return (alias != null);
699     }
700 
701     /***
702      * Set whether this table specified in the schema or is there just
703      * a foreign key reference to it.
704      * @param v  Value to assign to alias.
705      */
706     public void setAlias(String v)
707     {
708         this.alias = v;
709     }
710 
711 
712     /***
713      * Interface which objects for this table will implement
714      * @return value of interface.
715      */
716     public String getInterface()
717     {
718         return enterface;
719     }
720 
721     /***
722      * Interface which objects for this table will implement
723      * @param v  Value to assign to interface.
724      */
725     public void setInterface(String  v)
726     {
727         this.enterface = v;
728     }
729 
730     /***
731      * When a table is abstract, it marks the business object class that is
732      * generated as being abstract. If you have a table called "FOO", then the
733      * Foo BO will be <code>public abstract class Foo</code>
734      * This helps support class hierarchies
735      *
736      * @return value of abstractValue.
737      */
738     public boolean isAbstract()
739     {
740         return abstractValue;
741     }
742 
743     /***
744      * When a table is abstract, it marks the business object
745      * class that is generated as being abstract. If you have a
746      * table called "FOO", then the Foo BO will be
747      * <code>public abstract class Foo</code>
748      * This helps support class hierarchies
749      *
750      * @param v  Value to assign to abstractValue.
751      */
752     public void setAbstract(boolean  v)
753     {
754         this.abstractValue = v;
755     }
756 
757     /***
758      * Get the value of package.
759      *
760      * @return value of package.
761      */
762     public String getPackage()
763     {
764         if (pkg != null)
765         {
766             return pkg;
767         }
768         else
769         {
770             return this.getDatabase().getPackage();
771         }
772     }
773 
774     /***
775      * Set the value of package.
776      *
777      * @param v  Value to assign to package.
778      */
779     public void setPackage(String v)
780     {
781         this.pkg = v;
782     }
783 
784     /***
785      * Returns a List containing all the columns in the table
786      * 
787      * @return a List containing all the columns
788      */
789     public List getColumns()
790     {
791         return columnList;
792     }
793 
794     /***
795      * Utility method to get the number of columns in this table
796      */
797     public int getNumColumns()
798     {
799         return columnList.size();
800     }
801 
802     /***
803      * Returns a List containing all the FKs in the table
804      * 
805      * @return a List containing all the FKs
806      */
807     public List getForeignKeys()
808     {
809         return foreignKeys;
810     }
811 
812     /***
813      * Returns a Collection of parameters relevant for the chosen
814      * id generation method.
815      */
816     public List getIdMethodParameters()
817     {
818         return idMethodParameters;
819     }
820 
821     /***
822      * A name to use for creating a sequence if one is not specified.
823      *
824      * @return name of the sequence
825      */
826     public String getSequenceName()
827     {
828         String result = null;
829         if (getIdMethod().equals(NATIVE))
830         {
831             List idMethodParams = getIdMethodParameters();
832             if (idMethodParams == null)
833             {
834                 result = getName() + "_SEQ";
835             }
836             else
837             {
838                 result = ((IdMethodParameter) idMethodParams.get(0)).getValue();
839             }
840         }
841         return result;
842     }
843 
844     /***
845      * Returns a List containing all the indices in the table
846      *
847      * @return A List containing all the indices
848      */
849     public List getIndices()
850     {
851         return indices;
852     }
853 
854     /***
855      * Returns a List containing all the UKs in the table
856      *
857      * @return A List containing all the UKs
858      */
859     public List getUnices()
860     {
861         return unices;
862     }
863 
864     /***
865      * Returns a specified column.
866      *
867      * @param name name of the column
868      * @return Return a Column object or null if it does not exist.
869      */
870     public Column getColumn(String name)
871     {
872         return (Column) columnsByName.get(name);
873     }
874 
875     /***
876      * Returns a specified column.
877      *
878      * @param javaName java name of the column
879      * @return Return a Column object or null if it does not exist.
880      */
881     public Column getColumnByJavaName(String javaName)
882     {
883         return (Column) columnsByJavaName.get(javaName);
884     }
885 
886     /***
887      * Return the first foreign key that includes col in it's list
888      * of local columns.  Eg. Foreign key (a,b,c) refrences tbl(x,y,z)
889      * will be returned of col is either a,b or c.
890      *
891      * @param col column name included in the key
892      * @return Return a Column object or null if it does not exist.
893      */
894     public ForeignKey getForeignKey(String col)
895     {
896         ForeignKey firstFK = null;
897         for (Iterator iter = foreignKeys.iterator(); iter.hasNext();)
898         {
899             ForeignKey key = (ForeignKey) iter.next();
900             if (key.getLocalColumns().contains(col))
901             {
902                 if (firstFK == null)
903                 {
904                     firstFK = key;
905                 }
906                 else
907                 {
908                     //System.out.println(col+" is in multiple FKs.  This is not"
909                     //                   + " being handled properly.");
910                     //throw new IllegalStateException("Cannot call method if " +
911                     //    "column is referenced multiple times");
912                 }
913             }
914         }
915         return firstFK;
916     }
917 
918     /***
919      * Returns true if the table contains a specified column
920      *
921      * @param col the column
922      * @return true if the table contains the column
923      */
924     public boolean containsColumn(Column col)
925     {
926         return columnList.contains(col);
927     }
928 
929     /***
930      * Returns true if the table contains a specified column
931      *
932      * @param name name of the column
933      * @return true if the table contains the column
934      */
935     public boolean containsColumn(String name)
936     {
937         return (getColumn(name) != null);
938     }
939 
940     /***
941      * Set the parent of the table
942      *
943      * @param parent the parant database
944      */
945     public void setDatabase(Database parent)
946     {
947         tableParent = parent;
948     }
949 
950     /***
951      * Get the parent of the table
952      *
953      * @return the parant database
954      */
955     public Database getDatabase()
956     {
957         return tableParent;
958     }
959 
960     /***
961      * Flag to determine if code/sql gets created for this table.
962      * Table will be skipped, if return true.
963      * @return value of forReferenceOnly.
964      */
965     public boolean isForReferenceOnly()
966     {
967         return forReferenceOnly;
968     }
969 
970     /***
971      * Flag to determine if code/sql gets created for this table.
972      * Table will be skipped, if set to true.
973      * @param v  Value to assign to forReferenceOnly.
974      */
975     public void setForReferenceOnly(boolean  v)
976     {
977         this.forReferenceOnly = v;
978     }
979 
980     /***
981      * Returns a XML representation of this table.
982      *
983      * @return XML representation of this table
984      */
985     public String toString()
986     {
987         StringBuffer result = new StringBuffer();
988 
989         result.append ("<table name=\"")
990               .append(name)
991               .append('\"');
992 
993         if (javaName != null)
994         {
995             result.append(" javaName=\"")
996                   .append(javaName)
997                   .append('\"');
998         }
999 
1000         if (idMethod != null)
1001         {
1002             result.append(" idMethod=\"")
1003                   .append(idMethod)
1004                   .append('\"');
1005         }
1006 
1007         if (skipSql)
1008         {
1009             result.append(" skipSql=\"")
1010                   .append(new Boolean(skipSql))
1011                   .append('\"');
1012         }
1013 
1014         if (abstractValue)
1015         {
1016             result.append(" abstract=\"")
1017                   .append(new Boolean(abstractValue))
1018                   .append('\"');
1019         }
1020 
1021         if (baseClass != null)
1022         {
1023             result.append(" baseClass=\"")
1024                   .append(baseClass)
1025                   .append('\"');
1026         }
1027 
1028         if (basePeer != null)
1029         {
1030             result.append(" basePeer=\"")
1031                   .append(basePeer)
1032                   .append('\"');
1033         }
1034 
1035         result.append(">\n");
1036 
1037         if (columnList != null)
1038         {
1039             for (Iterator iter = columnList.iterator(); iter.hasNext();)
1040             {
1041                 result.append(iter.next());
1042             }
1043         }
1044 
1045         if (foreignKeys != null)
1046         {
1047             for (Iterator iter = foreignKeys.iterator(); iter.hasNext();)
1048             {
1049                 result.append(iter.next());
1050             }
1051         }
1052 
1053         if (idMethodParameters != null)
1054         {
1055             Iterator iter = idMethodParameters.iterator();
1056             while (iter.hasNext())
1057             {
1058                 result.append(iter.next());
1059             }
1060         }
1061 
1062         result.append ("</table>\n");
1063 
1064         return result.toString();
1065     }
1066 
1067     /***
1068      * Returns the collection of Columns which make up the single primary
1069      * key for this table.
1070      *
1071      * @return A list of the primary key parts.
1072      */
1073     public List getPrimaryKey()
1074     {
1075         List pk = new ArrayList(columnList.size());
1076 
1077         Iterator iter = columnList.iterator();
1078         while (iter.hasNext())
1079         {
1080             Column col = (Column) iter.next();
1081             if (col.isPrimaryKey())
1082             {
1083                 pk.add(col);
1084             }
1085         }
1086         return pk;
1087     }
1088 
1089     /***
1090      * Determine whether this table has a primary key.
1091      *
1092      * @return Whether this table has any primary key parts.
1093      */
1094     public boolean hasPrimaryKey()
1095     {
1096         return (getPrimaryKey().size() > 0);
1097     }
1098 
1099     /***
1100      * Returns all parts of the primary key, separated by commas.
1101      *
1102      * @return A CSV list of primary key parts.
1103      */
1104     public String printPrimaryKey()
1105     {
1106         return printList(columnList);
1107     }
1108 
1109     /***
1110      * Returns the elements of the list, separated by commas.
1111      *
1112      * @param list a list of Columns
1113      * @return A CSV list.
1114      */
1115     private String printList(List list)
1116     {
1117         StringBuffer result = new StringBuffer();
1118         boolean comma = false;
1119         for (Iterator iter = list.iterator(); iter.hasNext();)
1120         {
1121             Column col = (Column) iter.next();
1122             if (col.isPrimaryKey())
1123             {
1124                 if (comma)
1125                 {
1126                     result.append(',');
1127                 }
1128                 else
1129                 {
1130                     comma = true;
1131                 }
1132                 result.append(col.getName());
1133             }
1134         }
1135         return result.toString();
1136     }
1137 }