org.apache.ddlutils.model.Database.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ddlutils.model.Database.java

Source

package org.apache.ddlutils.model;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.Serializable;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ddlutils.dynabean.DynaClassCache;
import org.apache.ddlutils.dynabean.SqlDynaClass;
import org.apache.ddlutils.dynabean.SqlDynaException;

/**
 * Represents the database model, ie. the tables in the database. It also
 * contains the corresponding dyna classes for creating dyna beans for the
 * objects stored in the tables.
 *
 * @version $Revision: 636151 $
 */
public class Database implements Serializable {
    private final Log _log = LogFactory.getLog(getClass());

    /** Unique ID for serialization purposes. */
    private static final long serialVersionUID = -3160443396757573868L;

    /** The name of the database model. */
    private String _name;
    /** The method for generating primary keys (currently ignored). */
    private String _idMethod;
    /** The version of the model. */
    private String _version;
    /** The tables. */
    private ArrayList _tables = new ArrayList();
    /** The dyna class cache for this model. */
    private transient DynaClassCache _dynaClassCache = null;

    /**
     * Creates an empty model without a name.
     */
    public Database() {
    }

    /**
     * Creates an empty model with the given name.
     * 
     * @param name The name
     */
    public Database(String name) {
        _name = name;
    }

    /**
     * Adds all tables from the other database to this database.
     * Note that the other database is not changed.
     * 
     * @param otherDb The other database model
     */
    public void mergeWith(Database otherDb) throws ModelException {
        CloneHelper cloneHelper = new CloneHelper();

        for (int tableIdx = 0; tableIdx < otherDb.getTableCount(); tableIdx++) {
            Table table = otherDb.getTable(tableIdx);

            if (findTable(table.getName()) != null) {
                // TODO: It might make more sense to log a warning and overwrite the table (or merge them) ?
                throw new ModelException("Cannot merge the models because table " + table.getName()
                        + " already defined in this model");
            } else {
                addTable(cloneHelper.clone(table, true, false, this, true));
            }
        }
        for (int tableIdx = 0; tableIdx < otherDb.getTableCount(); tableIdx++) {
            Table otherTable = otherDb.getTable(tableIdx);
            Table localTable = findTable(otherTable.getName());

            for (int fkIdx = 0; fkIdx < otherTable.getForeignKeyCount(); fkIdx++) {
                ForeignKey fk = otherTable.getForeignKey(fkIdx);

                localTable.addForeignKey(cloneHelper.clone(fk, localTable, this, false));
            }
        }
    }

    /**
     * Returns the name of this database model.
     * 
     * @return The name
     */
    public String getName() {
        return _name;
    }

    /**
     * Sets the name of this database model.
     * 
     * @param name The name
     */
    public void setName(String name) {
        _name = name;
    }

    /**
     * Returns the version of this database model.
     * 
     * @return The version
     */
    public String getVersion() {
        return _version;
    }

    /**
     * Sets the version of this database model.
     * 
     * @param version The version
     */
    public void setVersion(String version) {
        _version = version;
    }

    /**
     * Returns the method for generating primary key values.
     * 
     * @return The method
     */
    public String getIdMethod() {
        return _idMethod;
    }

    /**
     * Sets the method for generating primary key values. Note that this
     * value is ignored by DdlUtils and only for compatibility with Torque.
     * 
     * @param idMethod The method
     */
    public void setIdMethod(String idMethod) {
        _idMethod = idMethod;
    }

    /**
     * Returns the number of tables in this model.
     * 
     * @return The number of tables
     */
    public int getTableCount() {
        return _tables.size();
    }

    /**
     * Returns the tables in this model.
     * 
     * @return The tables
     */
    public Table[] getTables() {
        return (Table[]) _tables.toArray(new Table[_tables.size()]);
    }

    /**
     * Returns the table at the specified position.
     * 
     * @param idx The index of the table
     * @return The table
     */
    public Table getTable(int idx) {
        return (Table) _tables.get(idx);
    }

    /**
     * Adds a table.
     * 
     * @param table The table to add
     */
    public void addTable(Table table) {
        if (table != null) {
            _tables.add(table);
        }
    }

    /**
     * Adds a table at the specified position.
     * 
     * @param idx   The index where to insert the table
     * @param table The table to add
     */
    public void addTable(int idx, Table table) {
        if (table != null) {
            _tables.add(idx, table);
        }
    }

    /**
     * Adds the given tables.
     * 
     * @param tables The tables to add
     */
    public void addTables(Collection tables) {
        for (Iterator it = tables.iterator(); it.hasNext();) {
            addTable((Table) it.next());
        }
    }

    /**
     * Removes the given table. This method does not check whether there are foreign keys to the table.
     * 
     * @param table The table to remove
     */
    public void removeTable(Table table) {
        if (table != null) {
            _tables.remove(table);
        }
    }

    /**
     * Removes the indicated table. This method does not check whether there are foreign keys to the table.
     * 
     * @param idx The index of the table to remove
     */
    public void removeTable(int idx) {
        _tables.remove(idx);
    }

    /**
     * Removes the given tables. This method does not check whether there are foreign keys to the tables.
     * 
     * @param tables The tables to remove
     */
    public void removeTables(Table[] tables) {
        _tables.removeAll(Arrays.asList(tables));
    }

    /**
     * Removes all but the given tables. This method does not check whether there are foreign keys to the
     * removed tables.
     * 
     * @param tables The tables to keep
     */
    public void removeAllTablesExcept(Table[] tables) {
        ArrayList allTables = new ArrayList(_tables);

        allTables.removeAll(Arrays.asList(tables));
        _tables.removeAll(allTables);
    }

    // Helper methods

    /**
     * Initializes the model by establishing the relationships between elements in this model encoded
     * eg. in foreign keys etc. Also checks that the model elements are valid (table and columns have
     * a name, foreign keys rference existing tables etc.) 
     */
    public void initialize() throws ModelException {
        // we have to setup
        // * target tables in foreign keys
        // * columns in foreign key references
        // * columns in indices
        // * columns in uniques
        HashSet namesOfProcessedTables = new HashSet();
        HashSet namesOfProcessedColumns = new HashSet();
        HashSet namesOfProcessedFks = new HashSet();
        HashSet namesOfProcessedIndices = new HashSet();
        int tableIdx = 0;

        if ((getName() == null) || (getName().length() == 0)) {
            throw new ModelException("The database model has no name");
        }

        for (Iterator tableIt = _tables.iterator(); tableIt.hasNext(); tableIdx++) {
            Table curTable = (Table) tableIt.next();

            if ((curTable.getName() == null) || (curTable.getName().length() == 0)) {
                throw new ModelException("The table nr. " + tableIdx + " has no name");
            }
            if (namesOfProcessedTables.contains(curTable.getName())) {
                throw new ModelException("There are multiple tables with the name " + curTable.getName());
            }
            namesOfProcessedTables.add(curTable.getName());

            namesOfProcessedColumns.clear();
            namesOfProcessedFks.clear();
            namesOfProcessedIndices.clear();

            for (int idx = 0; idx < curTable.getColumnCount(); idx++) {
                Column column = curTable.getColumn(idx);

                if ((column.getName() == null) || (column.getName().length() == 0)) {
                    throw new ModelException(
                            "The column nr. " + idx + " in table " + curTable.getName() + " has no name");
                }
                if (namesOfProcessedColumns.contains(column.getName())) {
                    throw new ModelException("There are multiple columns with the name " + column.getName()
                            + " in the table " + curTable.getName());
                }
                namesOfProcessedColumns.add(column.getName());

                if ((column.getType() == null) || (column.getType().length() == 0)) {
                    throw new ModelException(
                            "The column nr. " + idx + " in table " + curTable.getName() + " has no type");
                }
                if ((column.getTypeCode() == Types.OTHER) && !"OTHER".equalsIgnoreCase(column.getType())) {
                    throw new ModelException("The column nr. " + idx + " in table " + curTable.getName()
                            + " has an unknown type " + column.getType());
                }
                namesOfProcessedColumns.add(column.getName());
            }

            for (int idx = 0; idx < curTable.getForeignKeyCount(); idx++) {
                ForeignKey fk = curTable.getForeignKey(idx);
                String fkName = (fk.getName() == null ? "" : fk.getName());
                String fkDesc = (fkName.length() == 0 ? "nr. " + idx : fkName);

                if (fkName.length() > 0) {
                    if (namesOfProcessedFks.contains(fkName)) {
                        throw new ModelException("There are multiple foreign keys in table " + curTable.getName()
                                + " with the name " + fkName);
                    }
                    namesOfProcessedFks.add(fkName);
                }

                if (fk.getForeignTable() == null) {
                    Table targetTable = findTable(fk.getForeignTableName(), true);

                    if (targetTable == null) {
                        final String msg = String.format(
                                "The foreignkey [%s] in table [%s] references the undefined table [%s]. Will be ignored!",
                                fkDesc, curTable.getName(), fk.getForeignTableName());
                        _log.debug(msg);
                        continue;
                        //throw new ModelException(msg);
                    } else {
                        fk.setForeignTable(targetTable);
                    }
                }
                if (fk.getReferenceCount() == 0) {
                    throw new ModelException("The foreignkey " + fkDesc + " in table " + curTable.getName()
                            + " does not have any references");
                }
                for (int refIdx = 0; refIdx < fk.getReferenceCount(); refIdx++) {
                    Reference ref = fk.getReference(refIdx);

                    if (ref.getLocalColumn() == null) {
                        Column localColumn = curTable.findColumn(ref.getLocalColumnName(), true);

                        if (localColumn == null) {
                            throw new ModelException("The foreignkey " + fkDesc + " in table " + curTable.getName()
                                    + " references the undefined local column " + ref.getLocalColumnName());
                        } else {
                            ref.setLocalColumn(localColumn);
                        }
                    }
                    if (ref.getForeignColumn() == null) {
                        Column foreignColumn = fk.getForeignTable().findColumn(ref.getForeignColumnName(), true);

                        if (foreignColumn == null) {
                            throw new ModelException("The foreignkey " + fkDesc + " in table " + curTable.getName()
                                    + " references the undefined local column " + ref.getForeignColumnName()
                                    + " in table " + fk.getForeignTable().getName());
                        } else {
                            ref.setForeignColumn(foreignColumn);
                        }
                    }
                }
            }

            for (int idx = 0; idx < curTable.getIndexCount(); idx++) {
                Index index = curTable.getIndex(idx);
                String indexName = (index.getName() == null ? "" : index.getName());
                String indexDesc = (indexName.length() == 0 ? "nr. " + idx : indexName);

                if (indexName.length() > 0) {
                    if (namesOfProcessedIndices.contains(indexName)) {
                        throw new ModelException("There are multiple indices in table " + curTable.getName()
                                + " with the name " + indexName);
                    }
                    namesOfProcessedIndices.add(indexName);
                }
                if (index.getColumnCount() == 0) {
                    throw new ModelException("The index " + indexDesc + " in table " + curTable.getName()
                            + " does not have any columns");
                }

                for (int indexColumnIdx = 0; indexColumnIdx < index.getColumnCount(); indexColumnIdx++) {
                    IndexColumn indexColumn = index.getColumn(indexColumnIdx);
                    Column column = curTable.findColumn(indexColumn.getName(), true);

                    if (column == null) {
                        throw new ModelException("The index " + indexDesc + " in table " + curTable.getName()
                                + " references the undefined column " + indexColumn.getName());
                    } else {
                        indexColumn.setColumn(column);
                    }
                }
            }
        }
    }

    /**
     * Finds the table with the specified name, using case insensitive matching.
     * Note that this method is not called getTable to avoid introspection
     * problems.
     * 
     * @param name The name of the table to find
     * @return The table or <code>null</code> if there is no such table
     */
    public Table findTable(String name) {
        return findTable(name, false);
    }

    /**
     * Finds the table with the specified name, using case insensitive matching.
     * Note that this method is not called getTable) to avoid introspection
     * problems.
     * 
     * @param name          The name of the table to find
     * @param caseSensitive Whether case matters for the names
     * @return The table or <code>null</code> if there is no such table
     */
    public Table findTable(String name, boolean caseSensitive) {
        for (Iterator iter = _tables.iterator(); iter.hasNext();) {
            Table table = (Table) iter.next();

            if (caseSensitive) {
                if (table.getName().equals(name)) {
                    return table;
                }
            } else {
                if (table.getName().equalsIgnoreCase(name)) {
                    return table;
                }
            }
        }
        return null;
    }

    /**
     * Returns the indicated tables.
     * 
     * @param tableNames    The names of the tables
     * @param caseSensitive Whether the case of the table names matters
     * @return The tables
     */
    public Table[] findTables(String[] tableNames, boolean caseSensitive) {
        ArrayList tables = new ArrayList();

        if (tableNames != null) {
            for (int idx = 0; idx < tableNames.length; idx++) {
                Table table = findTable(tableNames[idx], caseSensitive);

                if (table != null) {
                    tables.add(table);
                }
            }
        }
        return (Table[]) tables.toArray(new Table[tables.size()]);
    }

    /**
     * Finds the tables whose names match the given regular expression.
     * 
     * @param tableNameRegExp The table name regular expression
     * @param caseSensitive   Whether the case of the table names matters; if not, then the regular expression should
     *                        assume that the table names are all-uppercase
     * @return The tables
     * @throws PatternSyntaxException If the regular expression is invalid
     */
    public Table[] findTables(String tableNameRegExp, boolean caseSensitive) throws PatternSyntaxException {
        ArrayList tables = new ArrayList();

        if (tableNameRegExp != null) {
            Pattern pattern = Pattern.compile(tableNameRegExp);

            for (Iterator tableIt = _tables.iterator(); tableIt.hasNext();) {
                Table table = (Table) tableIt.next();
                String tableName = table.getName();

                if (!caseSensitive) {
                    tableName = tableName.toUpperCase();
                }
                if (pattern.matcher(tableName).matches()) {
                    tables.add(table);
                }
            }
        }
        return (Table[]) tables.toArray(new Table[tables.size()]);
    }

    /**
     * Returns the dyna class cache. If none is available yet, a new one will be created.
     * 
     * @return The dyna class cache
     */
    private DynaClassCache getDynaClassCache() {
        if (_dynaClassCache == null) {
            _dynaClassCache = new DynaClassCache();
        }
        return _dynaClassCache;
    }

    /**
     * Resets the dyna class cache. This should be done for instance when a column
     * has been added or removed to a table.
     */
    public void resetDynaClassCache() {
        _dynaClassCache = null;
    }

    /**
     * Returns the {@link org.apache.ddlutils.dynabean.SqlDynaClass} for the given table name. If the it does not
     * exist yet, a new one will be created based on the Table definition.
     * 
     * @param tableName The name of the table to create the bean for
     * @return The <code>SqlDynaClass</code> for the indicated table or <code>null</code>
     *         if the model contains no such table
     */
    public SqlDynaClass getDynaClassFor(String tableName) {
        Table table = findTable(tableName);

        return table != null ? getDynaClassCache().getDynaClass(table) : null;
    }

    /**
     * Returns the {@link org.apache.ddlutils.dynabean.SqlDynaClass} for the given dyna bean.
     * 
     * @param bean The dyna bean
     * @return The <code>SqlDynaClass</code> for the given bean
     */
    public SqlDynaClass getDynaClassFor(DynaBean bean) {
        return getDynaClassCache().getDynaClass(bean);
    }

    /**
     * Creates a new dyna bean for the given table.
     * 
     * @param table The table to create the bean for
     * @return The new dyna bean
     */
    public DynaBean createDynaBeanFor(Table table) throws SqlDynaException {
        return getDynaClassCache().createNewInstance(table);
    }

    /**
     * Convenience method that combines {@link #createDynaBeanFor(Table)} and
     * {@link #findTable(String, boolean)}.
     * 
     * @param tableName     The name of the table to create the bean for
     * @param caseSensitive Whether case matters for the names
     * @return The new dyna bean
     */
    public DynaBean createDynaBeanFor(String tableName, boolean caseSensitive) throws SqlDynaException {
        return getDynaClassCache().createNewInstance(findTable(tableName, caseSensitive));
    }

    /**
     * {@inheritDoc}
     */
    public boolean equals(Object obj) {
        if (obj instanceof Database) {
            Database other = (Database) obj;

            // Note that this compares case sensitive
            return new EqualsBuilder().append(_name, other._name).append(_tables, other._tables).isEquals();
        } else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public int hashCode() {
        return new HashCodeBuilder(17, 37).append(_name).append(_tables).toHashCode();
    }

    /**
     * {@inheritDoc}
     */
    public String toString() {
        StringBuffer result = new StringBuffer();

        result.append("Database [name=");
        result.append(getName());
        result.append("; ");
        result.append(getTableCount());
        result.append(" tables]");

        return result.toString();
    }

    /**
     * Returns a verbose string representation of this database.
     * 
     * @return The string representation
     */
    public String toVerboseString() {
        StringBuffer result = new StringBuffer();

        result.append("Database [");
        result.append(getName());
        result.append("] tables:");
        for (int idx = 0; idx < getTableCount(); idx++) {
            result.append(" ");
            result.append(getTable(idx).toVerboseString());
        }

        return result.toString();
    }
}