package org.jumpmind.db.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 static org.apache.commons.lang.StringUtils.isNotBlank; import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; import org.apache.commons.collections.set.ListOrderedSet; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; /** * Represents a database foreign key. */ public class ForeignKey implements Cloneable, Serializable { private static final long serialVersionUID = 1L; /** The name of the foreign key, may be <code>null</code>. */ private String name; /** The target table. */ private Table foreignTable; /** The name of the foreign table. */ private String foreignTableName; /** The references between local and remote columns. */ private ListOrderedSet references = new ListOrderedSet(); /** Whether this foreign key has an associated auto-generated index. */ private boolean autoIndexPresent; /** * Creates a new foreign key object that has no name. */ public ForeignKey() { this(null); } /** * Creates a new foreign key object. * * @param name * The name of the foreign key */ public ForeignKey(String name) { this(name, null); } /** * Creates a new foreign key object. * * @param name * The name of the foreign key * @param foreignKeyTableName * The name of the foreign key table */ public ForeignKey(String name, String foreignKeyTableName) { this.name = name; this.foreignTableName = foreignKeyTableName; } /** * Returns the name of this foreign key. * * @return The name */ public String getName() { return name; } /** * Sets the name of this foreign key. * * @param name * The name */ public void setName(String name) { this.name = name; } /** * Returns the foreign table. * * @return The foreign table */ public Table getForeignTable() { return foreignTable; } /** * Sets the foreign table. * * @param foreignTable * The foreign table */ public void setForeignTable(Table foreignTable) { this.foreignTable = foreignTable; this.foreignTableName = (foreignTable == null ? null : foreignTable.getName()); } /** * Returns the name of the foreign table. * * @return The table name */ public String getForeignTableName() { return foreignTableName; } /** * Sets the name of the foreign table. Please note that you should not use * this method when manually constructing or manipulating the database * model. Rather utilize the {@link #setForeignTable(Table)} method. * * @param foreignTableName * The table name */ public void setForeignTableName(String foreignTableName) { if ((foreignTable != null) && !foreignTable.getName().equals(foreignTableName)) { foreignTable = null; } this.foreignTableName = foreignTableName; } /** * Returns the number of references. * * @return The number of references */ public int getReferenceCount() { return references.size(); } /** * Returns the indicated reference. * * @param idx * The index * @return The reference */ public Reference getReference(int idx) { return (Reference) references.get(idx); } /** * Returns the references. * * @return The references */ public Reference[] getReferences() { return (Reference[]) references.toArray(new Reference[references.size()]); } /** * Returns the first reference if it exists. * * @return The first reference */ public Reference getFirstReference() { return (Reference) (references.isEmpty() ? null : references.get(0)); } /** * Adds a reference, ie. a mapping between a local column (in the table that * owns this foreign key) and a remote column. * * @param reference * The reference to add */ public void addReference(Reference reference) { if (reference != null) { for (int idx = 0; idx < references.size(); idx++) { Reference curRef = getReference(idx); if (curRef.getSequenceValue() > reference.getSequenceValue()) { references.add(idx, reference); return; } } references.add(reference); } } /** * Removes the given reference. * * @param reference * The reference to remove */ public void removeReference(Reference reference) { if (reference != null) { references.remove(reference); } } /** * Removes the indicated reference. * * @param idx * The index of the reference to remove */ public void removeReference(int idx) { references.remove(idx); } /** * Determines whether this foreign key uses the given column as a local * column in a reference. * * @param column * The column to check * @return <code>true</code> if a reference uses the column as a local * column */ public boolean hasLocalColumn(Column column) { for (int idx = 0; idx < getReferenceCount(); idx++) { if (column.equals(getReference(idx).getLocalColumn())) { return true; } } return false; } /** * Determines whether this foreign key uses the given column as a foreign * column in a reference. * * @param column * The column to check * @return <code>true</code> if a reference uses the column as a foreign * column */ public boolean hasForeignColumn(Column column) { for (int idx = 0; idx < getReferenceCount(); idx++) { if (column.equals(getReference(idx).getForeignColumn())) { return true; } } return false; } /** * Determines whether this foreign key has an auto-generated associated * index. * * @return <code>true</code> if an auto-generated index exists */ public boolean isAutoIndexPresent() { return autoIndexPresent; } /** * Specifies whether this foreign key has an auto-generated associated * index. * * @param autoIndexPresent * <code>true</code> if an auto-generated index exists */ public void setAutoIndexPresent(boolean autoIndexPresent) { this.autoIndexPresent = autoIndexPresent; } /** * {@inheritDoc} */ public Object clone() throws CloneNotSupportedException { ForeignKey result = (ForeignKey) super.clone(); result.name = name; result.foreignTableName = foreignTableName; result.references = new ListOrderedSet(); for (Iterator<?> it = references.iterator(); it.hasNext();) { result.references.add(((Reference) it.next()).clone()); } return result; } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof ForeignKey) { ForeignKey otherFk = (ForeignKey) obj; // Note that this compares case sensitive // Note also that we can simply compare the references regardless of // their order // (which is irrelevant for fks) because they are contained in a set EqualsBuilder builder = new EqualsBuilder(); if (isCheckName(otherFk)) { builder.append(name, otherFk.name); } builder.append(foreignTableName, otherFk.foreignTableName); builder.append(references.size(), otherFk.references.size()); for (int i = 0; i < references.size() && i < otherFk.references.size(); i++) { builder.append(references.get(i), otherFk.references.get(i)); } return builder.isEquals(); } else { return false; } } /** * Compares this foreign key to the given one while ignoring the case of * identifiers. * * @param otherFk * The other foreign key * @return <code>true</code> if this foreign key is equal (ignoring case) to * the given one */ @SuppressWarnings("unchecked") public boolean equalsIgnoreCase(ForeignKey otherFk) { boolean checkName = isCheckName(otherFk); if ((!checkName || name.equalsIgnoreCase(otherFk.name)) && foreignTableName.equalsIgnoreCase(otherFk.foreignTableName)) { HashSet<Reference> otherRefs = new HashSet<Reference>(); otherRefs.addAll(otherFk.references); for (Iterator<?> it = references.iterator(); it.hasNext();) { Reference curLocalRef = (Reference) it.next(); boolean found = false; for (Iterator<?> otherIt = otherRefs.iterator(); otherIt.hasNext();) { Reference curOtherRef = (Reference) otherIt.next(); if (curLocalRef.equalsIgnoreCase(curOtherRef)) { otherIt.remove(); found = true; break; } } if (!found) { return false; } } return otherRefs.isEmpty(); } else { return false; } } private boolean isCheckName(ForeignKey otherFk) { return name != null && name.length() > 0 && otherFk.name != null && otherFk.name.length() > 0; } /** * {@inheritDoc} */ public int hashCode() { HashCodeBuilder builder = new HashCodeBuilder(17, 37).append(foreignTableName).append( references); if (isNotBlank(name)) { builder.append(name); } return builder.toHashCode(); } /** * {@inheritDoc} */ public String toString() { StringBuffer result = new StringBuffer(); result.append("Foreign key ["); if ((getName() != null) && (getName().length() > 0)) { result.append("name="); result.append(getName()); result.append("; "); } result.append("foreign table="); result.append(getForeignTableName()); result.append("; "); result.append(getReferenceCount()); result.append(" references]"); return result.toString(); } /** * Returns a verbose string representation of this foreign key. * * @return The string representation */ public String toVerboseString() { StringBuffer result = new StringBuffer(); result.append("ForeignK ky ["); if ((getName() != null) && (getName().length() > 0)) { result.append("name="); result.append(getName()); result.append("; "); } result.append("foreign table="); result.append(getForeignTableName()); result.append("] references:"); for (int idx = 0; idx < getReferenceCount(); idx++) { result.append(" "); result.append(getReference(idx).toString()); } return result.toString(); } }