001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
006: * Copyright (C) 2005-2006 Continuent, Inc.
007: * Contact: sequoia@continuent.org
008: *
009: * Licensed under the Apache License, Version 2.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.apache.org/licenses/LICENSE-2.0
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: *
021: * Initial developer(s): Emmanuel Cecchet.
022: * Contributor(s): Mathieu Peltier, Sara Bouchenak.
023: */package org.continuent.sequoia.common.sql.schema;
024:
025: import java.io.Serializable;
026: import java.sql.SQLException;
027: import java.util.ArrayList;
028: import java.util.Iterator;
029:
030: import org.continuent.sequoia.common.locks.TransactionLogicalLock;
031: import org.continuent.sequoia.common.xml.DatabasesXmlTags;
032:
033: /**
034: * A <code>DatabaseTable</code> represents a database table ! It is just an
035: * array of <code>TableColumns</code> objects.
036: * <p>
037: * Keep it mind that <code>ArrayList</code> is not synchronized...
038: *
039: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet</a>
040: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier</a>
041: * @author <a href="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak</a>
042: * @version 1.0
043: */
044: public class DatabaseTable implements Serializable {
045: private static final long serialVersionUID = 7138810420058450235L;
046:
047: /** Schema name this table belongs to */
048: private String schema;
049:
050: /** Database table name. */
051: private String name;
052:
053: /** <code>ArrayList</code> of <code>DatabaseColumn</code>. */
054: private ArrayList columns;
055:
056: /** Lock for this table */
057: private transient TransactionLogicalLock lock = new TransactionLogicalLock();
058:
059: /**
060: * List of tables that must be locked when this table is updated. This is
061: * usually the list of tables that have a foreign key referencing this table.<br>
062: * <code>ArrayList</code> of <code>String</code> (table names)
063: */
064: private ArrayList dependingTables;
065:
066: /**
067: * Creates a new <code>DatabaseTable</code> instance.
068: *
069: * @param name table name
070: */
071: public DatabaseTable(String name) {
072: this (name, new ArrayList());
073: }
074:
075: /**
076: * Creates a new <code>DatabaseTable</code> instance.
077: *
078: * @param name table name
079: * @param nbOfColumns number of columns
080: */
081: public DatabaseTable(String name, int nbOfColumns) {
082: this (name, new ArrayList(nbOfColumns));
083: }
084:
085: /**
086: * Creates a new <code>DatabaseTable</code> instance. New table shares
087: * (only) its name and columns with the table passed as parameter.
088: *
089: * @param dt database table
090: */
091: public DatabaseTable(DatabaseTable dt) {
092: this (dt.getName(), dt.getColumns());
093: if (dt.getDependingTables() != null)
094: for (Iterator i = dt.getDependingTables().iterator(); i
095: .hasNext();)
096: addDependingTable((String) i.next());
097: }
098:
099: /**
100: * Creates a new <code>DatabaseTable</code> instance.
101: *
102: * @param name table name
103: * @param columns columns list
104: */
105: private DatabaseTable(String name, ArrayList columns) {
106: if (name == null)
107: throw new IllegalArgumentException(
108: "Illegal null database table name in DatabaseTable constructor");
109:
110: this .name = name;
111: this .columns = columns;
112: }
113:
114: /**
115: * Add a depending table to this table. A depending table is locked when the
116: * current table is updated. A depending table is usually a table that has a
117: * foreign key referencing the current table.<br>
118: * This method takes care of duplicates and a table name is only inserted
119: * once.
120: *
121: * @param tableName the depending table to add
122: */
123: public synchronized void addDependingTable(String tableName) {
124: if (dependingTables == null)
125: dependingTables = new ArrayList();
126: if (!dependingTables.contains(tableName))
127: dependingTables.add(tableName);
128: }
129:
130: /**
131: * Adds a <code>DatabaseColumn</code> object to this table.
132: * <p>
133: * Warning! The underlying <code>ArrayList</code> is not synchronized.
134: *
135: * @param column a <code>DatabaseColumn</code> value
136: */
137: public void addColumn(DatabaseColumn column) {
138: columns.add(column);
139: }
140:
141: /**
142: * Returns a list of <code>DatabaseColumn</code> objects describing the
143: * columns of this table.
144: * <p>
145: * Warning! The underlying <code>ArrayList</code> is not synchronized.
146: *
147: * @return an <code>ArrayList</code> of <code>DatabaseColumn</code>
148: */
149: public ArrayList getColumns() {
150: return columns;
151: }
152:
153: /**
154: * Returns the <code>DatabaseColumn</code> object matching the given column
155: * name or <code>null</code> if not found (the case is ignored).
156: *
157: * @param columnName column name to look for
158: * @return a <code>DatabaseColumn</code> value or <code>null</code>
159: */
160: public DatabaseColumn getColumn(String columnName) {
161: DatabaseColumn c;
162: for (Iterator i = columns.iterator(); i.hasNext();) {
163: c = (DatabaseColumn) i.next();
164: if (columnName.equalsIgnoreCase(c.getName()))
165: return c;
166:
167: }
168: return null;
169: }
170:
171: /**
172: * Returns the <code>DatabaseColumn</code> object matching the given column
173: * name or <code>null</code> if not found (the case can be enforced).
174: *
175: * @param columnName column name to look for
176: * @param isCaseSensitive true if name matching must be case sensitive
177: * @return a <code>DatabaseColumn</code> value or <code>null</code>
178: */
179: public DatabaseColumn getColumn(String columnName,
180: boolean isCaseSensitive) {
181: if (!isCaseSensitive)
182: return getColumn(columnName);
183:
184: DatabaseColumn c;
185: for (Iterator i = columns.iterator(); i.hasNext();) {
186: c = (DatabaseColumn) i.next();
187: if (columnName.equals(c.getName()))
188: return c;
189:
190: }
191: return null;
192: }
193:
194: /**
195: * Returns the dependingTables value.
196: *
197: * @return Returns the dependingTables.
198: */
199: public ArrayList getDependingTables() {
200: return dependingTables;
201: }
202:
203: /**
204: * Returns the lock for this table.
205: *
206: * @return a <code>TransactionLogicalLock</code> instance
207: */
208: public TransactionLogicalLock getLock() {
209: return lock;
210: }
211:
212: /**
213: * Retain locks held by a transaction when the schema is reloaded.
214: *
215: * @param oldTable the previous version of this table
216: */
217: void setLock(DatabaseTable oldTable) {
218: lock = oldTable.lock;
219: }
220:
221: /**
222: * Gets the name of the table.
223: *
224: * @return the table name
225: */
226: public String getName() {
227: return name;
228: }
229:
230: /**
231: * Returns the schema value.
232: *
233: * @return Returns the schema.
234: */
235: public String getSchema() {
236: return schema;
237: }
238:
239: /**
240: * Sets the schema value.
241: *
242: * @param schema The schema to set.
243: */
244: public void setSchema(String schema) {
245: this .schema = schema;
246: }
247:
248: /**
249: * Returns a list of <code>DatabaseColumn</code> objects representing the
250: * unique columns of this table.
251: * <p>
252: * Warning! The underlying <code>ArrayList</code> is not synchronized.
253: *
254: * @return an <code>ArrayList</code> of <code>DatabaseColumn</code>
255: * objects
256: */
257: public ArrayList getUniqueColumns() {
258: ArrayList cols = new ArrayList();
259: Iterator i;
260: DatabaseColumn c;
261:
262: for (i = columns.iterator(); i.hasNext();) {
263: c = (DatabaseColumn) i.next();
264: if (c.isUnique())
265: cols.add(c);
266: }
267: return cols;
268: }
269:
270: /**
271: * Merges this table with the given table. Both columns and depending tables
272: * are merged.
273: * <p>
274: * All missing columns are added if no conflict is detected. An exception is
275: * thrown if the given table columns conflicts with this one.
276: *
277: * @param table the table to merge
278: * @throws SQLException if the schemas conflict
279: */
280: public void merge(DatabaseTable table) throws SQLException {
281: if (table == null)
282: return;
283:
284: if (dependingTables == null)
285: dependingTables = table.getDependingTables();
286: else {
287: ArrayList otherDependingTables = table.getDependingTables();
288: if (otherDependingTables != null)
289: for (Iterator iter = otherDependingTables.iterator(); iter
290: .hasNext();)
291: addDependingTable((String) iter.next());
292: }
293:
294: ArrayList otherColumns = table.getColumns();
295: if (otherColumns == null)
296: return;
297:
298: DatabaseColumn c, original;
299: int size = otherColumns.size();
300: for (int i = 0; i < size; i++) {
301: c = (DatabaseColumn) otherColumns.get(i);
302: original = getColumn(c.getName());
303: if (original == null)
304: addColumn(c);
305: else {
306: if (!original.equalsIgnoreType(c))
307: throw new SQLException("Unable to merge table ["
308: + table.getName() + "]: column '"
309: + c.getName() + "' definition mismatch");
310: }
311: }
312: }
313:
314: /**
315: * Drops a <code>DatabaseColumn</code> object from this table.
316: * <p>
317: * Warning! The underlying <code>ArrayList</code> is not synchronized.
318: *
319: * @param columnName a <code>String</code> that maps to a
320: * <code>DatabaseColumn</code> value
321: */
322: public void removeColumn(String columnName) {
323: columns.remove(getColumn(columnName));
324: }
325:
326: /**
327: * Drops a <code>DatabaseColumn</code> object from this table.
328: * <p>
329: * Warning! The underlying <code>ArrayList</code> is not synchronized.
330: *
331: * @param column a <code>DatabaseColumn</code> value
332: */
333: public void removeColumn(DatabaseColumn column) {
334: columns.remove(column);
335: }
336:
337: /**
338: * Updates this table with the given table's columns. All missing columns are
339: * added and if the given table columns conflicts with this one, the current
340: * column definition is overriden with the provided one. Note that existing
341: * locks (if any) are preserved.
342: * <p>
343: * Warning! Data structures of the given table are not cloned.
344: *
345: * @param table the table to use to update the current one
346: */
347: public void updateColumns(DatabaseTable table) {
348: if (table == null)
349: return;
350:
351: ArrayList otherColumns = table.getColumns();
352:
353: // Remove columns that do not exist anymore in new schema
354: for (Iterator iter = columns.iterator(); iter.hasNext();) {
355: DatabaseColumn c = (DatabaseColumn) iter.next();
356: if (!otherColumns.contains(c))
357: iter.remove();
358: }
359:
360: // Add missing columns and update existing ones
361: int size = otherColumns.size();
362: for (int i = 0; i < size; i++) {
363: DatabaseColumn c = (DatabaseColumn) otherColumns.get(i);
364: DatabaseColumn originalColumn = getColumn(c.getName());
365: if (originalColumn == null)
366: addColumn(c);
367: else {
368: originalColumn.setType(c.getType());
369: originalColumn.setIsUnique(c.isUnique());
370: }
371: }
372: }
373:
374: /**
375: * Two <code>DatabaseTable</code> are considered equal if they have the same
376: * name and the same columns.
377: *
378: * @param other the object to compare with
379: * @return <code>true</code> if the tables are equal
380: */
381: public boolean equals(Object other) {
382: if ((other == null) || !(other instanceof DatabaseTable))
383: return false;
384:
385: DatabaseTable t = (DatabaseTable) other;
386:
387: // Compare name
388: if (!name.equals(t.getName()))
389: return false;
390:
391: // Compare schema
392: if (t.getSchema() == null) {
393: if (schema != null)
394: return false;
395: } else {
396: if (!t.getSchema().equals(schema))
397: return false;
398: }
399: // Compare columns
400: if (t.getColumns() == null)
401: return columns == null;
402: else
403: return t.getColumns().equals(columns);
404: }
405:
406: /**
407: * This function is the same as equals but ignores the column type.
408: *
409: * @param other the object to compare with
410: * @return <code>true</code> if the table are equal ignoring the columns
411: * type
412: * @see #equals(Object)
413: */
414: public boolean equalsIgnoreType(Object other) {
415: if ((other == null) || !(other instanceof DatabaseTable))
416: return false;
417:
418: DatabaseTable t = (DatabaseTable) other;
419: // Compare name
420: if (!name.equals(t.getName()))
421: return false;
422:
423: // Compare schema
424: if (t.getSchema() == null) {
425: if (schema != null)
426: return false;
427: } else {
428: if (!t.getSchema().equals(schema))
429: return false;
430: }
431:
432: DatabaseColumn c1, c2;
433: Iterator iter = columns.iterator();
434: while (iter.hasNext()) {
435: c1 = (DatabaseColumn) iter.next();
436: c2 = t.getColumn(c1.getName());
437:
438: if (!c1.equalsIgnoreType(c2))
439: return false; // Not compatible
440: }
441: return true;
442: }
443:
444: /**
445: * Get xml information about this table.
446: *
447: * @return xml formatted information on this database table.
448: */
449: public String getXml() {
450: StringBuffer info = new StringBuffer();
451: info.append("<" + DatabasesXmlTags.ELT_DatabaseTable + " "
452: + DatabasesXmlTags.ATT_tableName + "=\"" + name + "\" "
453: + DatabasesXmlTags.ATT_nbOfColumns + "=\""
454: + columns.size() + "\">");
455: for (int i = 0; i < columns.size(); i++)
456: info.append(((DatabaseColumn) columns.get(i)).getXml());
457: info.append("</" + DatabasesXmlTags.ELT_DatabaseTable + ">");
458: return info.toString();
459: }
460:
461: /**
462: * @see java.lang.Object#toString()
463: */
464: public String toString() {
465: return name + "(" + columns + ")";
466: }
467: }
|