blob: 8fede8d78a24701085e8bbe1fc4b9ed4e9192c5c [file] [log] [blame]
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.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;
import org.apache.ddlutils.util.StringUtils;
/**
* Represents a database foreign key.
*
* @version $Revision$
*/
public class ForeignKey implements Serializable
{
/** Unique ID for serialization purposes. */
private static final long serialVersionUID = 7833254626253719913L;
/** 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 action to perform when the value of the referenced column changes. */
private CascadeActionEnum _onUpdate = CascadeActionEnum.NONE;
/** The action to perform when the referenced row is deleted. */
private CascadeActionEnum _onDelete = CascadeActionEnum.NONE;
/** 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)
{
_name = name;
}
/**
* 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)
{
_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)
{
_foreignTable = foreignTable;
_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;
}
_foreignTableName = foreignTableName;
}
/**
* Returns the action for this foreignkey for when the referenced row is deleted.
*
* @return The action
*/
public CascadeActionEnum getOnDelete()
{
return _onDelete;
}
/**
* Sets the action for this foreignkey for when the referenced row is deleted.
*
* @param onDelete The action
* @throws NullPointerException If <code>onDelete</code> is null
*/
public void setOnDelete(CascadeActionEnum onDelete) throws NullPointerException
{
if (onDelete == null)
{
throw new NullPointerException("The onDelete action cannot be null");
}
_onDelete = onDelete;
}
/**
* Returns the action for this foreignkey for when the referenced row is changed.
*
* @return The action
*/
public CascadeActionEnum getOnUpdate()
{
return _onUpdate;
}
/**
* Sets the action for this foreignkey for when the referenced row is changed.
*
* @param onUpdate The action
* @throws NullPointerException If <code>onUdate</code> is null
*/
public void setOnUpdate(CascadeActionEnum onUpdate) throws NullPointerException
{
if (onUpdate == null)
{
throw new NullPointerException("The onUpdate action cannot be null");
}
_onUpdate = onUpdate;
}
/**
* 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 indicated column as a local
* column in a reference. This method assumes that the caller checked
* already that the column is a column in the table owning this foreign key.
*
* @param columnName The name of the column to check
* @param caseSensitive Whether case matters when checking for the column's name
* @return <code>true</code> if a reference uses the column as a local
* column
*/
public boolean hasLocalColumn(String columnName, boolean caseSensitive)
{
for (int idx = 0; idx < getReferenceCount(); idx++)
{
if (StringUtils.equals(columnName, getReference(idx).getLocalColumnName(), caseSensitive))
{
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 uses the given column as a foreign
* column in a reference. This method assumes that the caller already checked
* whether this foreign key references the tale owning the indicate column.
*
* @param columnName The name of the column to check
* @param caseSensitive Whether case matters when checking for the column's name
* @return <code>true</code> if a reference uses the column as a foreign
* column
*/
public boolean hasForeignColumn(String columnName, boolean caseSensitive)
{
for (int idx = 0; idx < getReferenceCount(); idx++)
{
if (StringUtils.equals(columnName, getReference(idx).getForeignColumnName(), caseSensitive))
{
return true;
}
}
return false;
}
/**
* Determines whether this foreign key has an auto-generated associated index. Note that
* this is a hint for the platform and has no relevancy to the model itself.
*
* @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. Note that
* this is a hint set by the model reader and has no relevancy to the model itself.
*
* @param autoIndexPresent <code>true</code> if an auto-generated index exists
*/
public void setAutoIndexPresent(boolean autoIndexPresent)
{
_autoIndexPresent = autoIndexPresent;
}
/**
* {@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 ((_name != null) && (_name.length() > 0) && (otherFk._name != null) && (otherFk._name.length() > 0))
{
builder.append(_name, otherFk._name);
}
return builder.append(_foreignTableName, otherFk._foreignTableName)
.append(_references, otherFk._references)
.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
*/
public boolean equalsIgnoreCase(ForeignKey otherFk)
{
boolean checkName = (_name != null) && (_name.length() > 0) &&
(otherFk._name != null) && (otherFk._name.length() > 0);
if ((!checkName || _name.equalsIgnoreCase(otherFk._name)) &&
_foreignTableName.equalsIgnoreCase(otherFk._foreignTableName))
{
HashSet otherRefs = new HashSet();
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;
}
}
/**
* {@inheritDoc}
*/
public int hashCode()
{
return new HashCodeBuilder(17, 37).append(_name)
.append(_foreignTableName)
.append(_references)
.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();
}
}