View Javadoc

1   package org.apache.torque.engine.database.model;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Hashtable;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import org.apache.commons.collections.map.ListOrderedMap;
29  import org.apache.commons.lang.StringUtils;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  import org.apache.torque.engine.EngineException;
35  
36  import org.xml.sax.Attributes;
37  
38  /**
39   * Data about a table used in an application.
40   * 
41   * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
42   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
43   * @author <a href="mailto:mpoeschl@marmot.at>Martin Poeschl</a>
44   * @author <a href="mailto:jmcnally@collab.net>John McNally</a>
45   * @author <a href="mailto:dlr@collab.net>Daniel Rall</a>
46   * @author <a href="mailto:byron_foster@byron_foster@yahoo.com>Byron Foster</a>
47   * @author <a href="mailto:monroe@dukece.com>Greg Monroe</a>
48   * @version $Id: Table.java,v 1.1 2007-10-21 07:57:27 abyrne Exp $
49   */
50  public class Table implements IDMethod {
51  	/** Logging class from commons.logging */
52  	private static Log log = LogFactory.getLog(Table.class);
53  
54  	// private AttributeListImpl attributes;
55  	private List columnList;
56  	private List foreignKeys;
57  	private List indices;
58  	private List unices;
59  	private List idMethodParameters;
60  	private String name;
61  	private String description;
62  	private String javaName;
63  	private String idMethod;
64  	private String javaNamingMethod;
65  	private Database tableParent;
66  	private List referrers;
67  	private List foreignTableNames;
68  	private boolean containsForeignPK;
69  	private Column inheritanceColumn;
70  	private boolean skipSql;
71  	private boolean abstractValue;
72  	private String alias;
73  	private String enterface;
74  	private String pkg;
75  	private String baseClass;
76  	private String basePeer;
77  	private Hashtable columnsByName;
78  	private Hashtable columnsByJavaName;
79  	private boolean needsTransactionInPostgres;
80  	private boolean heavyIndexing;
81  	private boolean forReferenceOnly;
82  	private Map options;
83  
84  	/**
85  	 * Default Constructor
86  	 */
87  	public Table() {
88  		this(null);
89  	}
90  
91  	/**
92  	 * Constructs a table object with a name
93  	 * 
94  	 * @param name
95  	 *            table name
96  	 */
97  	public Table(String name) {
98  		this.name = name;
99  		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 }