/*
* Copyright (c) 2008, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.sqlobject;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang.NotImplementedException;
import org.apache.log4j.Logger;
import ca.sqlpower.object.AbstractSPObject;
import ca.sqlpower.object.ObjectDependentException;
import ca.sqlpower.object.SPListener;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.object.annotation.Accessor;
import ca.sqlpower.object.annotation.Mutator;
import ca.sqlpower.object.annotation.NonProperty;
import ca.sqlpower.object.annotation.Transient;
import ca.sqlpower.sql.jdbcwrapper.DatabaseMetaDataDecorator;
import ca.sqlpower.util.SQLPowerUtils;
import com.google.common.collect.ListMultimap;
/**
* SQLObject is the main base class of the Architect API. All objects that can
* be reverse-engineered from or forward-engineered to an SQL database are
* represented as SQLObject subclasses. The main features inherited from
* SQLObject are:
*
* <h2>Tree structure</h2>
*
* SQLObjects are arranged in a tree structure: each object has a parent, which
* is also a SQLObject, and it has a list of children which point back to it.
* All children of any given SQLObject must be of the exact same type. This is
* enforced in several places, so you should find out quickly if you break this
* rule.
*
* <h2>Transparent lazy reverse engineering</h2>
*
* SQLObjects have two primary states: populated and unpopulated. The state
* transitions from unpopulated to populated when the child list is filled in by
* reverse engineering the information from a physical SQL database. The state
* never transitions from populated to unpopulated.
* <p>
* When creating a SQLObject, you can decide whether you want it to start in the
* populated state or not. When starting in the populated state, the lazy
* reverse engineering feature will not be active, and the SQLObject can (must)
* be completely configured via its API.
*
* <h2>Event System</h2>
*
* Most changes to the state of a SQLObject cause an event to be fired. This is
* useful when building GUI components and undo/redo systems around SQLObjects.
* See {@link SQLObjectEvent} and {@link SQLObjectListener} for details.
*
* <h2>Client Properties</h2>
*
* Every SQLObject maintains a map of key/value pairs. This map is segregated
* into namespaces to ensure multiple clients who don't know about each other do
* not end up suffering naming collisions.
*/
public abstract class SQLObject extends AbstractSPObject implements java.io.Serializable {
private static Logger logger = Logger.getLogger(SQLObject.class);
protected boolean populated = false;
private AtomicBoolean populating = new AtomicBoolean(false);
/**
* The name used for this object in a physical database system. This name may have
* to be altered to fit the naming constraints of a particular system, in terms
* of length, case, allowable characters, and other requirements.
*/
private String physicalName;
/**
* The map that hold the client properties of this object. Don't modify the
* contents of this map directly; use the {@link #putClientProperty(Class, String, Object)}
* and {@link #getClientProperty(Class, String)} methods which take care of
* firing events and other such bookkeeping.
*/
private final Map<String, Object> clientProperties = new HashMap<String, Object>();
/**
* This is the throwable that tells if the children of this component can be
* reached or not. If the exception at the child type is null then the
* children can be reached. If it is not null then there was an exception
* the last time the children were attempted to be accessed. There can also
* be an exception at SQLObject itself if an exception occurred that
* prevented the children from populating and was either not tied to a
* specific child type or not enough information is known to tell which
* child type it actually failed on.
*/
private final Map<Class<? extends SQLObject>, Throwable> childrenInaccessibleReason =
new HashMap<Class<? extends SQLObject>, Throwable>();
/**
* Returns the name used for this object in a physical database system. This
* name may have to be altered to fit the naming constraints of a particular
* system, in terms of length, case, allowable characters, duplication of
* names within the same namespace, and other requirements. Presently, the
* DDL Generators take it upon themselves to perform this alteration. In the
* near future, we hope to make the DDL Generators use this name verbatim
* and simply fail on names that are not permissible. The responsibility of
* suggesting physical name changes will shift to critics configured for
* each database platform's particular needs. Those critics would provide
* "quick fix" suggestions with names that are legal in their own target
* platform, and then set them.
* <p>
* there is no good reason why this method is declared final, but there is
* no good reason to override it at this time.
*
* @return The physical name to use for forward engineering and database
* comparison, or the logical name (see {@link #getName()}) if no
* physical name has been set.
*/
@Accessor(isInteresting=true)
public final String getPhysicalName() {
if (physicalName != null) {
return physicalName;
}
return getName();
}
/**
* Sets the physical identifier name to use when forward-engineering into
* the target database and comparing with existing databases.
*
* @param argName The new physical name to use.
*/
@Mutator
public void setPhysicalName(String argName) {
String oldPhysicalName = getPhysicalName();
String actualOldPhysicalName = physicalName;
this.physicalName = argName;
//The old physicalName returned from getPhysicalName must be the same
//as that returned by getPhysicalName or the persisters will fail. However,
//if the physical name is being set to null when it was null we do not want
//to fire an event.
if ((actualOldPhysicalName == null && argName == null)
|| (actualOldPhysicalName != null && actualOldPhysicalName.equals(argName))) return;
firePropertyChange("physicalName",oldPhysicalName,argName);
}
/**
* Causes this SQLObject to load its children through populateImpl (if any exist).
* This will do nothing if the object is already populated.
*/
public final synchronized void populate() throws SQLObjectException {
if (populated || !populating.compareAndSet(false, true)) return;
// We're going to just leave caching on all the time and see how it pans out
DatabaseMetaDataDecorator.putHint(
DatabaseMetaDataDecorator.CACHE_TYPE,
DatabaseMetaDataDecorator.CacheType.EAGER_CACHE);
childrenInaccessibleReason.clear();
try {
populateImpl();
} catch (final SQLObjectException e) {
runInForeground(new Runnable() {
public void run() {
try {
setChildrenInaccessibleReason(e, SQLObject.class, true);
} catch (SQLObjectException e) {
throw new RuntimeException(e);
}
}
});
} catch (final RuntimeException e) {
runInForeground(new Runnable() {
public void run() {
try {
setChildrenInaccessibleReason(e, SQLObject.class, true);
} catch (SQLObjectException e) {
throw new RuntimeException(e);
}
}
});
} finally {
populating.set(false);
}
}
/**
* Causes this SQLObject to load its children (if any exist).
* This method will be called lots of times, so track whether or
* not you need to do anything and return right away whenever
* possible.
*/
protected abstract void populateImpl() throws SQLObjectException;
/**
* Returns a short string that should be displayed to the user for
* representing this SQLObject as a label.
*/
@Transient @Accessor
public abstract String getShortDisplayName();
/**
* Tells if this object has already been filled with children, or
* if that operation is still pending.
*/
@Accessor
public boolean isPopulated() {
return populated;
}
/**
* Lets outside users modify the internal flag that says whether
* or not the list of child objects has already been loaded from
* the source database. Users of this SQLObject hierarchies should
* not normally call this method, but it needs to be public for the
* SwingUIProject load implementation.
*/
@Mutator
public void setPopulated(boolean v) {
boolean oldPop = populated;
populated = v;
firePropertyChange("populated", oldPop, v);
}
/**
* Returns true if and only if this object can have child
* SQLObjects. Your implementation of this method <b>must not</b>
* cause JDBC activity, or the lazy loading properties of your
* SQLObjects will be wasted!
*/
@Override
public boolean allowsChildren() {
return super.allowsChildren();
}
@Override
public boolean removeChild(SPObject child) throws ObjectDependentException,
IllegalArgumentException {
if (child instanceof SQLObject) {
if (!fireDbChildPreRemove(getChildrenWithoutPopulating(child.getClass()).indexOf(child), (SQLObject) child)) {
return false;
}
}
if (!getChildrenWithoutPopulating().contains(child)) {
return false;
}
return removeChildImpl(child);
}
/**
* Returns an unmodifiable view of the child list. All list
* members will be SQLObject subclasses (SQLTable,
* SQLRelationship, SQLColumn, etc.) which are directly contained
* within this SQLObject.
*/
@NonProperty
public List<? extends SQLObject> getChildren() {
return getChildren(SQLObject.class);
}
@NonProperty
public <T extends SPObject> List<T> getChildren(Class<T> type) {
try {
if (isMagicEnabled()) {
populate();
}
return getChildrenWithoutPopulating(type);
} catch (SQLObjectException e) {
throw new RuntimeException("Could not populate " + getName(), e);
}
}
/**
* Returns a new and unmodifiable list of all SQLObjects currently children
* of this object. The list of objects is unmodifiable as children cannot
* be added or removed through it. The list is a new list instead of wrapping
* the list in an unmodifiable list to let the list be updated on one thread
* while it is being iterated over on another thread.
* <p>
* Calling this method will not cause the object to populate.
* @return
*/
@NonProperty
public abstract List<? extends SQLObject> getChildrenWithoutPopulating();
@NonProperty
public <T extends SPObject> List<T> getChildrenWithoutPopulating(Class<T> type) {
List<T> children = new ArrayList<T>();
for (SQLObject child : getChildrenWithoutPopulating()) {
if (type.isAssignableFrom(child.getClass())) {
children.add(type.cast(child));
}
}
return Collections.unmodifiableList(children);
}
@NonProperty
public SQLObject getChild(int index) throws SQLObjectException {
populate();
return (SQLObject) getChildrenWithoutPopulating().get(index);
}
@NonProperty
public int getChildCount() throws SQLObjectException {
populate();
return getChildrenWithoutPopulating().size();
}
/**
* Returns the names of all children of this SQLObject. Causes this
* SQLObject to become populated.
* <p>
* Originally created for internal use during refresh. There should be no
* harm in making this method public if it's needed externally.
*
* @throws SQLObjectException
* if populating this object fails
*/
@NonProperty
Set<String> getChildNames() throws SQLObjectException {
return getChildNames(SQLObject.class);
}
/**
* Returns the names of all children of a certain type of this SQLObject.
* Causes this SQLObject to become populated.
* <p>
* Originally created for internal use during refresh. There should be no
* harm in making this method public if it's needed externally.
*
* @throws SQLObjectException
* if populating this object fails
*/
@NonProperty
<T extends SQLObject> Set<String> getChildNames(Class<T> childType) {
HashSet<String> names = new HashSet<String>();
for (T child : getChildren(childType)) {
names.add(child.getName());
}
return names;
}
/**
* Adds the given SQLObject to this SQLObject at index. Causes a
* DBChildrenInserted event. If you want to override the
* behaviour of addChild, override this method.
* @throws SQLObjectException
*/
// public void addChild(SQLObject newChild, int index) throws SQLObjectException {
// addChildImpl(newChild, index);
// }
/**
* Adds the given SQLObject to this SQLObject at the end of the
* child list by calling {@link #addChild(SPObject, int)}. Causes
* a DBChildrenInserted event. If you want to override the
* behaviour of addChild, do not override this method.
* @throws SQLObjectException
*/
public void addChild(SQLObject newChild) throws SQLObjectException {
addChild(newChild, getChildrenWithoutPopulating(newChild.getClass()).size());
}
// ------------------- sql object event support -------------------
/*
* @return An immutable copy of the list of SQLObject listeners
*/
@NonProperty
public List<SPListener> getSPListeners() {
return listeners;
}
// ------------------- sql object Pre-event support -------------------
private final transient List<SQLObjectPreEventListener> sqlObjectPreEventListeners =
new ArrayList<SQLObjectPreEventListener>();
/**
* @return An immutable copy of the list of SQLObject pre-event listeners
*/
@NonProperty
public List<SQLObjectPreEventListener> getSQLObjectPreEventListeners() {
return sqlObjectPreEventListeners;
}
public void addSQLObjectPreEventListener(SQLObjectPreEventListener l) {
if (l == null) throw new NullPointerException("You can't add a null listener");
synchronized(sqlObjectPreEventListeners) {
if (sqlObjectPreEventListeners.contains(l)) {
if (logger.isDebugEnabled()) {
logger.debug("NOT Adding duplicate pre-event listener "+l+" to SQLObject "+this);
}
return;
}
sqlObjectPreEventListeners.add(l);
}
}
public void removeSQLObjectPreEventListener(SQLObjectPreEventListener l) {
synchronized(sqlObjectPreEventListeners) {
sqlObjectPreEventListeners.remove(l);
}
}
/**
* Fires a pre-remove event, and returns the status of whether or not the
* operation should proceed.
*
* @param oldIndices The child indices that might be removed
* @param oldChildren The children that might be removed
* @return True if the operation should proceed; false if it should not.
*/
protected boolean fireDbChildrenPreRemove(int[] oldIndices, List<SQLObject> oldChildren) {
if (logger.isDebugEnabled()) {
logger.debug(getClass().getName()+" "+toString()+": " +
"firing dbChildrenPreRemove event");
}
SQLObjectPreEvent e = new SQLObjectPreEvent
(this,
oldIndices,
(SQLObject[]) oldChildren.toArray(new SQLObject[oldChildren.size()]));
int count = 0;
synchronized (sqlObjectPreEventListeners) {
SQLObjectPreEventListener[] listeners =
sqlObjectPreEventListeners.toArray(new SQLObjectPreEventListener[0]);
for (SQLObjectPreEventListener l : listeners) {
l.dbChildrenPreRemove(e);
count++;
}
}
if (logger.isDebugEnabled()) logger.debug("Notified "+count+" listeners. Veto="+e.isVetoed());
return !e.isVetoed();
}
/**
* Convenience method for {@link #fireDbChildrenPreRemove(int[], List)} when there
* is only one child being removed.
*
* @param oldIndex The index of the child to be removed
* @param oldChild The child to be removed
*/
protected boolean fireDbChildPreRemove(int oldIndex, SQLObject oldChild) {
int[] oldIndexArray = new int[1];
oldIndexArray[0] = oldIndex;
List<SQLObject> oldChildList = new ArrayList<SQLObject>(1);
oldChildList.add(oldChild);
return fireDbChildrenPreRemove(oldIndexArray, oldChildList);
}
@NonProperty
public <T extends SQLObject> T getChildByName(String name, Class<T> childType) {
return getChildByNameImpl(name, false, childType);
}
@NonProperty
public <T extends SQLObject> T getChildByNameIgnoreCase(String name, Class<T> childType) {
return getChildByNameImpl(name, true, childType);
}
/**
* Searches for a child object based on its class type, name and case
* sensitivity.
*
* @param <T>
* The child type of SQLObject to look for
* @param name
* The name of the child
* @param ignoreCase
* Whether the name search should be case sensitive
* @param childType
* The child type to look for when searching for the SQLObject's
* name
* @return The found child with the given name, or null if it does not exist.
*/
@NonProperty
private <T extends SQLObject> T getChildByNameImpl(String name, boolean ignoreCase, Class<T> childType) {
for (T o : getChildren(childType)) {
if ( (ignoreCase && o.getName().equalsIgnoreCase(name))
|| ( (!ignoreCase) && o.getName().equals(name)) ) {
return o;
}
}
return null;
}
/**
* Returns the index of the named child, or -1 if there is no child with
* that name.
*
* @param name The name of the child to look for (case sensitive)
* @return The index of the named child in the child list, or -1 if there
* is no such child.
* @throws SQLObjectException if the child list can't be populated
*/
@NonProperty
public int getIndexOfChildByName(String name) throws SQLObjectException {
int i = 0;
for (Object o : getChildren()) {
SQLObject so = (SQLObject) o;
if (so.getName().equals(name)) {
return i;
}
i++;
}
return -1;
}
/**
* Sets the current value of the named client property in the given
* namespace. If the new value is different from the existing value,
* a SQLObjectChangedEvent will be fired with the property name
* of <code>namespace.getName() + "." + propName</code>.
*
* @param namespace
* The namespace to look in. This is usually the class of the
* code calling in, but there is no restriction from getting and
* setting client properties maintained by other classes.
* @param propName
* The name of the property to set.
*/
public void putClientProperty(Class<?> namespace, String propName, Object property) {
String key = namespace + "." + propName;
Object oldValue = clientProperties.get(key);
clientProperties.put(key, property);
firePropertyChange("clientProperty." + key, oldValue, property);
}
/**
* Returns the current value of the named client property in the given
* namespace.
*
* @param namespace
* The namespace to look in. This is usually the class of the
* code calling in, but there is no restriction from getting and
* setting client properties maintained by other classes.
* @param propName
* The name of the property to get.
* @return The property's current value, or null if the property is not set
* on this SQL Object.
*/
@NonProperty
public Object getClientProperty(Class<?> namespace, String propName) {
return clientProperties.get(namespace + "." + propName);
}
/**
* Rerturns the property names of all client properties currently set
* on this SQLObject.
*/
@NonProperty
public Set<String> getClientPropertyNames() {
return clientProperties.keySet();
}
@Transient @Accessor
public Throwable getChildrenInaccessibleReason(Class<? extends SQLObject> childType) {
return childrenInaccessibleReason.get(childType);
}
@Transient @Accessor
public Map<Class<? extends SQLObject>, Throwable> getChildrenInaccessibleReasons() {
return Collections.unmodifiableMap(childrenInaccessibleReason);
}
/**
* This setter will take in a Throwable to set the inaccessible reason to,
* for things like copy methods. See {@link SQLObject#childrenInaccessibleReason};
*
* @param cause
* The throwable that made the children of this object
* inaccessible
* @param childType
* The type of child that is inaccessible. Can be
* {@link SQLObject} if the exception covers all of the children.
* @param rethrow
* Decides if the cause should be rethrown wrapped in a
* SQLObjectException. Set this to true to have the exception be
* rethrown.
*/
@Transient @Mutator
public void setChildrenInaccessibleReason(Throwable cause,
Class<? extends SQLObject> childType, boolean rethrow) throws SQLObjectException {
Map<Class<? extends SQLObject>, Throwable> oldVal =
new HashMap<Class<? extends SQLObject>, Throwable>(this.childrenInaccessibleReason);
this.childrenInaccessibleReason.put(childType, cause);
firePropertyChange("childrenInaccessibleReason", oldVal, childrenInaccessibleReason);
setPopulated(true);
if (rethrow) {
if (cause instanceof SQLObjectException) {
throw (SQLObjectException) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
throw new SQLObjectException(cause);
}
}
}
/**
* A basic refresh method that simply calls refresh on all existing
* children. Most classes will have to override this with an implementation
* that checks for changes in the physical database and adjusts the child
* list accordingly.
* <p>
* Note that this package-private method is not meant to be called directly!
* All refresh operations have to be initiated by calling the parent database's
* refresh method, {@link SQLDatabase#refresh()}, which is public.
*/
void refresh() throws SQLObjectException {
if (!isPopulated()) {
logger.debug("Not refreshing unpopulated object " + this);
return;
}
if (isTableContainer()) {
logger.debug("Refreshing table container " + this);
Connection con = null;
SQLDatabase db = SQLPowerUtils.getAncestor(this, SQLDatabase.class);
try {
SQLCatalog cat = SQLPowerUtils.getAncestor(this, SQLCatalog.class);
SQLSchema sch = SQLPowerUtils.getAncestor(this, SQLSchema.class);
String catName = cat == null ? null : cat.getName();
String schName = sch == null ? null : sch.getName();
con = db.getConnection();
DatabaseMetaData dbmd = con.getMetaData();
final List<SQLTable> newChildren = SQLTable.fetchTablesForTableContainer(
dbmd, catName, schName);
runInForeground(new Runnable() {
public void run() {
try {
SQLObjectUtils.refreshChildren(SQLObject.this, newChildren, SQLTable.class);
} catch (SQLObjectException e) {
throw new SQLObjectRuntimeException(e);
}
}
});
try {
final ListMultimap<String, SQLColumn> newCols = SQLColumn.fetchColumnsForTable(
catName, schName, null, dbmd);
runInForeground(new Runnable() {
public void run() {
List<SQLTable> populatedTables = new ArrayList<SQLTable>();
for (SQLTable table : getChildrenWithoutPopulating(SQLTable.class)) {
if (table.isColumnsPopulated()) {
populatedTables.add(table);
}
}
for (SQLTable table : populatedTables) {
try {
SQLObjectUtils.refreshChildren(table, newCols.get(table.getName()), SQLColumn.class);
} catch (SQLObjectException e) {
throw new SQLObjectRuntimeException(e);
}
}
}
});
} catch (SQLException e) {
throw new SQLObjectException("Refresh failed", e);
}
for (SQLTable t : getChildrenWithoutPopulating(SQLTable.class)) {
t.refreshIndexes();
}
for (SQLTable t : getChildrenWithoutPopulating(SQLTable.class)) {
t.refreshExportedKeys();
}
logger.debug("Table container refresh complete for " + this);
} catch (SQLException e) {
throw new SQLObjectException("Refresh failed", e);
} finally {
try {
if (con != null) con.close();
} catch (SQLException ex) {
logger.warn("Failed to close connection! Squishing this exception:", ex);
}
}
} else {
for (SQLObject o : getChildrenWithoutPopulating()) {
o.refresh();
}
}
}
/**
* Returns true if this SQLObject is definitely a container for SQLTable
* objects. Depending on the source database topology, instances of
* SQLDatabase, SQLCatalog, and SQLSchema may return true. Other types of
* SQLObject will always return false, since there is no topology in which
* they are table containers. Calling this method will never result in
* populating an unpopulated SQLObject.
* <p>
* If this SQLObject is populated and has at least one child, this method
* makes the determination cheap and accurate by checking if the children
* are of type SQLTable. Otherwise, the object has no children (whether or
* not it is populated), so this method examines the JDBC driver's database
* metadata to determine the topology based on the reported catalogTerm and
* schemaTerm. A null value for either term is interpreted to mean the
* database does not have that level of object containment. The (major)
* downside of this approach is that it does not work when the database
* connection is unavailable.
*
* @return
* @throws SQLObjectException
* if the determination requires database metadata access, and
* it's not possible to obtain the database connection or the
* database metadata.
*/
@NonProperty
public boolean isTableContainer() throws SQLObjectException {
// first, check for existing SQLTable children--this is a dead giveaway for a table container!
if (getChildrenWithoutPopulating().size() > 0) {
return (getChildrenWithoutPopulating(SQLTable.class).size() != 0);
}
// no children. we have to do a bit of structural investigation.
// schemas can only contain tables
if (getClass() == SQLSchema.class) {
return true;
}
// determination for catalogs and databases requires database metadata
Connection con = null;
try {
// catalogs could contain schemas or tables. If schemaTerm is null, it must be tables.
if (getClass() == SQLCatalog.class) {
SQLDatabase db = (SQLDatabase) getParent();
con = db.getConnection();
if (con == null) {
throw new SQLObjectException("Unable to determine table container status without database connection");
}
DatabaseMetaData dbmd = con.getMetaData();
return dbmd.getSchemaTerm() == null;
}
// databases could contain catalogs, schemas, or tables
if (getClass() == SQLDatabase.class) {
SQLDatabase db = (SQLDatabase) this;
con = db.getConnection();
if (con == null) {
throw new SQLObjectException("Unable to determine table container status without database connection");
}
DatabaseMetaData dbmd = con.getMetaData();
return (dbmd.getSchemaTerm() == null) && (dbmd.getCatalogTerm() == null);
}
} catch (SQLException ex) {
throw new SQLObjectException("Failed to obtain database metadata", ex);
} finally {
try {
if (con != null) con.close();
} catch (SQLException ex) {
logger.warn("Failed to close connection", ex);
}
}
// other types of SQLObject are never table containers
return false;
}
/**
* Updates all the properties of this SQLObject to match those of the other
* SQLObject. Some implementations also update the list of children (see
* SQLIndex and SQLRelationship for examples of this). If any of the properties
* of this object change as a result of the update, the corresponding
* events will be fired.
*
* @param source
* The SQLObject to read the new property values from. Must be of
* the same type as the receiving object.
* @throws SQLObjectException if the attempted update causes a populate that fails
* @throws NotImplementedException
* The default implementation from SQLObject just throws this
* exception. Subclasses that want to implement this
* functionality must override this method.
* @throws ClassCastException
* if the given source object is not the same type as this
* SQLObject.
*/
public void updateToMatch(SQLObject source) throws SQLObjectException {
throw new UnsupportedOperationException();
}
/**
* Updates the physical name to be the same as the logical name if the
* current physical name and the old logical name match or if the physical
* name is missing.
*
* @param oldName
* The name before the latest name change. Used to check if the
* physical name is a match.
* @param newName
* The name the logical name is being changed to. If the old name
* and physical name match the physical name will be set to this
* one.
*/
protected void updatePhysicalNameToMatch(String oldName, String newName) {
if ((newName != null && getPhysicalName() == null)
|| (getPhysicalName() != null && "".equals(getPhysicalName().trim()))
|| (oldName != null && oldName.equals(getPhysicalName()))) {
setPhysicalName(newName);
}
}
}