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