/*

   Derby - Class org.apache.derby.impl.sql.depend.BasicDependencyManager

   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.

 */

package org.apache.derby.impl.sql.depend;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.apache.derby.catalog.DependableFinder;
import org.apache.derby.catalog.UUID;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.io.FormatableBitSet;
import org.apache.derby.iapi.services.sanity.SanityManager;
import org.apache.derby.iapi.sql.compile.CompilerContext;
import org.apache.derby.iapi.sql.compile.Parser;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.sql.conn.StatementContext;
import org.apache.derby.iapi.sql.depend.Dependency;
import org.apache.derby.iapi.sql.depend.DependencyManager;
import org.apache.derby.iapi.sql.depend.Dependent;
import org.apache.derby.iapi.sql.depend.Provider;
import org.apache.derby.iapi.sql.depend.ProviderInfo;
import org.apache.derby.iapi.sql.depend.ProviderList;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.sql.dictionary.DependencyDescriptor;
import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
import org.apache.derby.iapi.sql.dictionary.ViewDescriptor;
import org.apache.derby.iapi.store.access.TransactionController;
import org.apache.derby.impl.sql.compile.CreateViewNode;

/**
 * The dependency manager tracks needs that dependents have of providers.
 * <p>
 * A dependency can be either persistent or non-persistent. Persistent
 * dependencies are stored in the data dictionary, and non-persistent
 * dependencies are stored within the dependency manager itself (in memory).
 * <p>
 * <em>Synchronization:</em> The need for synchronization is different depending
 * on whether the dependency is an in-memory dependency or a stored dependency.
 * When accessing and modifying in-memory dependencies, Java synchronization
 * must be used (specifically, we synchronize on {@code this}). When accessing
 * and modifying stored dependencies, which are stored in the data dictionary,
 * we expect that the locking protocols will provide the synchronization needed.
 * Note that stored dependencies should not be accessed while holding the
 * monitor of {@code this}, as this may result in deadlocks. So far the need
 * for synchronization across both in-memory and stored dependencies hasn't
 * occured.
 */
public class BasicDependencyManager implements DependencyManager {
  
    /**
     * DataDictionary for this database.
     */
    private final DataDictionary dd;
    
    /**
     * Map of in-memory dependencies for Dependents.
     * In-memory means that one or both of the Dependent
     * or Provider are non-persistent (isPersistent() returns false).
     * 
     * Key is the UUID of the Dependent (from getObjectID()).
     * Value is a List containing Dependency objects, each
     * of whihc links the same Dependent to a Provider.
     * Dependency objects in the List are unique.
     * 
     */
    //@GuardedBy("this")
    private final Map dependents = new HashMap();
    
    /**
     * Map of in-memory dependencies for Providers.
     * In-memory means that one or both of the Dependent
     * or Provider are non-persistent (isPersistent() returns false).
     * 
     * Key is the UUID of the Provider (from getObjectID()).
     * Value is a List containing Dependency objects, each
     * of which links the same Provider to a Dependent.
     * Dependency objects in the List are unique.
     * 
     */    
    //@GuardedBy("this")
    private final Map providers = new HashMap();


	//
	// DependencyManager interface
	//

	/**
		adds a dependency from the dependent on the provider.
		This will be considered to be the default type of
		dependency, when dependency types show up.
		<p>
		Implementations of addDependency should be fast --
		performing alot of extra actions to add a dependency would
		be a detriment.

		@param d the dependent
		@param p the provider

		@exception StandardException thrown if something goes wrong
	 */
	public void addDependency(Dependent d, Provider p, ContextManager cm) 
		throws StandardException {
		addDependency(d, p, cm, null);
	}

    /**
     * Adds the dependency to the data dictionary or the in-memory dependency
     * map.
     * <p>
     * The action taken is detmermined by whether the dependent and/or the
     * provider are persistent.
     *
     * @param d the dependent
     * @param p the provider
     * @param cm context manager
     * @param tc transaction controller, used to determine if any transactional
     *      operations should be attempted carried out in a nested transaction.
     *      If {@code tc} is {@code null}, the user transaction is used.
     * @throws StandardException if adding the dependency fails
     */
    private void addDependency(Dependent d, Provider p,
                               ContextManager cm, TransactionController tc)
            throws StandardException {
        // Dependencies are either in-memory or stored, but not both.
        if (! d.isPersistent() || ! p.isPersistent()) {
            addInMemoryDependency(d, p, cm);
        } else {
            addStoredDependency(d, p, cm, tc);
        }
    }

    /**
     * Adds the dependency as an in-memory dependency.
     *
     * @param d the dependent
     * @param p the provider
     * @param cm context manager
     * @throws StandardException if adding the dependency fails
     * @see #addStoredDependency
     */
    private synchronized void addInMemoryDependency(Dependent d, Provider p,
                                                    ContextManager cm)
            throws StandardException {
        Dependency dy = new BasicDependency(d, p);

        // Duplicate dependencies are not added to the lists.
        // If we find that the dependency we are trying to add in
        // one list is a duplicate, then it should be a duplicate in the
        // other list.
        boolean addedToDeps = false;
        boolean addedToProvs = false;

        addedToDeps = addDependencyToTable(dependents, d.getObjectID(), dy);
        if (addedToDeps) {
            addedToProvs = addDependencyToTable(providers, p.getObjectID(), dy);
        } else if (SanityManager.DEBUG) {
            addedToProvs = addDependencyToTable(providers, p.getObjectID(), dy);
        }

        // Dependency should have been added to both or neither.
        if (SanityManager.DEBUG) {
            if (addedToDeps != addedToProvs) {
                SanityManager.THROWASSERT(
                    "addedToDeps (" + addedToDeps +
                    ") and addedToProvs (" +
                    addedToProvs + ") are expected to agree");
            }
        }

        // Add the dependency to the StatementContext, so that
        // it can be cleared on a pre-execution error.
        StatementContext sc = (StatementContext) cm.getContext(
                org.apache.derby.iapi.reference.ContextId.LANG_STATEMENT);
        sc.addDependency(dy);
    }

    /**
     * Adds the dependency as a stored dependency.
     * <p>
     * We expect that transactional locking (in the data dictionary) is enough
     * to protect us from concurrent changes when adding stored dependencies.
     * Adding synchronization here and accessing the data dictionary (which is
     * transactional) may cause deadlocks.
     *
     * @param d the dependent
     * @param p the provider
     * @param cm context manager
     * @param tc transaction controller (may be {@code null})
     * @throws StandardException if adding the dependency fails
     * @see #addInMemoryDependency
     */
    private void addStoredDependency(Dependent d, Provider p,
                                     ContextManager cm,
                                     TransactionController tc)
            throws StandardException {
        LanguageConnectionContext lcc = getLanguageConnectionContext(cm);
        // tc == null means do it in the user transaction
        TransactionController tcToUse =
                (tc == null) ? lcc.getTransactionExecute() : tc;

        // Call the DataDictionary to store the dependency.
        dd.addDescriptor(new DependencyDescriptor(d, p), null,
                         DataDictionary.SYSDEPENDS_CATALOG_NUM, true,
                         tcToUse);
    }

	/**
		drops a single dependency

		@param d the dependent
		@param p the provider

		@exception StandardException thrown if something goes wrong
	 */
	private	void dropDependency(LanguageConnectionContext lcc, Dependent d, Provider p) throws StandardException
	{
		if (SanityManager.DEBUG) {
			// right now, this routine isn't called for in-memory dependencies
			if (! d.isPersistent() || ! p.isPersistent())
			{
				SanityManager.NOTREACHED();
			}
		}

		DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(d, p);

		dd.dropStoredDependency( dependencyDescriptor, 
								 lcc.getTransactionExecute() );
	}


	/**
		mark all dependencies on the named provider as invalid.
		When invalidation types show up, this will use the default
		invalidation type. The dependencies will still exist once
		they are marked invalid; clearDependencies should be used
		to remove dependencies that a dependent has or provider gives.
		<p>
		Implementations of this can take a little time, but are not
		really expected to recompile things against any changes
		made to the provider that caused the invalidation. The
		dependency system makes no guarantees about the state of
		the provider -- implementations can call this before or
		after actually changing the provider to its new state.
		<p>
		Implementations should throw StandardException
		if the invalidation should be disallowed.

		@param p the provider
		@param action	The action causing the invalidate

		@exception StandardException thrown if unable to make it invalid
	 */
	public void invalidateFor(Provider p, int action,
				  LanguageConnectionContext lcc)
		 throws StandardException
	{
		/*
		** Non-persistent dependencies are stored in memory, and need to
		** use "synchronized" to ensure their lists don't change while
		** the invalidation is taking place.  Persistent dependencies are
		** stored in the data dictionary, and we should *not* do anything
		** transactional (like reading from a system table) from within
		** a synchronized method, as it could cause deadlock.
		**
		** Presumably, the transactional locking in the data dictionary
		** is enough to protect us, so that we don't have to put any
		** synchronization in the DependencyManager.
		*/
		if (p.isPersistent())
			coreInvalidateFor(p, action, lcc);
		else {
			synchronized (this) {
				coreInvalidateFor(p, action, lcc);
			}
		}
	}

    /**
     * A version of invalidateFor that does not provide synchronization among
     * invalidators.
     *
     * @param p the provider
     * @param action the action causing the invalidation
     * @param lcc language connection context
     *
     * @throws StandardException if something goes wrong
     */
	private void coreInvalidateFor(Provider p, int action, LanguageConnectionContext lcc)
		throws StandardException
	{
		List list = getDependents(p);

        if (list.isEmpty()) {
			return;
		}


		// affectedCols is passed in from table descriptor provider to indicate
		// which columns it cares; subsetCols is affectedCols' intersection
		// with column bit map found in the provider of SYSDEPENDS line to
		// find out which columns really matter.  If SYSDEPENDS line's
		// dependent is view (or maybe others), provider is table, yet it 
		// doesn't have column bit map because the view was created in a
		// previous version of server which doesn't support column dependency,
		// and we really want it to have (such as in drop column), in any case
		// if we passed in table descriptor to this function with a bit map,
		// we really need this, we generate the bitmaps on the fly and update
		// SYSDEPENDS

		FormatableBitSet affectedCols = null, subsetCols = null;
		if (p instanceof TableDescriptor)
		{
			affectedCols = ((TableDescriptor) p).getReferencedColumnMap();
			if (affectedCols != null)
				subsetCols = new FormatableBitSet(affectedCols.getLength());
		}

		{
			StandardException noInvalidate = null;
			// We cannot use an iterator here as the invalidations can remove
			// entries from this list. 
			for (int ei = list.size() - 1; ei >= 0; ei--)
			{
				if (ei >= list.size())
					continue;
				Dependency dependency = (Dependency) list.get(ei);

				Dependent dep = dependency.getDependent();

				if (affectedCols != null)
				{
					TableDescriptor td = (TableDescriptor) dependency.getProvider();
					FormatableBitSet providingCols = td.getReferencedColumnMap();
					if (providingCols == null)
					{
						if (dep instanceof ViewDescriptor)
						{
							ViewDescriptor vd = (ViewDescriptor) dep;
							SchemaDescriptor compSchema;
							compSchema = dd.getSchemaDescriptor(vd.getCompSchemaId(), null);
							CompilerContext newCC = lcc.pushCompilerContext(compSchema);
							Parser	pa = newCC.getParser();

							// Since this is always nested inside another SQL
							// statement, so topLevel flag should be false
							CreateViewNode cvn = (CreateViewNode)pa.parseStatement(
												vd.getViewText());

							// need a current dependent for bind
							newCC.setCurrentDependent(dep);
							cvn.bindStatement();
							ProviderInfo[] providerInfos = cvn.getProviderInfo();
							lcc.popCompilerContext(newCC);

							boolean		interferent = false;
							for (int i = 0; i < providerInfos.length; i++)
							{
								Provider provider = null;
									provider = (Provider) providerInfos[i].
													getDependableFinder().
													getDependable(dd,
													providerInfos[i].getObjectId());
								if (provider instanceof TableDescriptor)
								{
									TableDescriptor tab = (TableDescriptor)provider;
									FormatableBitSet colMap = tab.getReferencedColumnMap();
									if (colMap == null)
										continue;
									// if later on an error is raised such as in
									// case of interference, this dependency line
									// upgrade will not happen due to rollback
									tab.setReferencedColumnMap(null);
									dropDependency(lcc, vd, tab);
									tab.setReferencedColumnMap(colMap);
									addDependency(vd, tab, lcc.getContextManager());

									if (tab.getObjectID().equals(td.getObjectID()))
									{
										System.arraycopy(affectedCols.getByteArray(), 0, 
											subsetCols.getByteArray(), 0, 
											affectedCols.getLengthInBytes());
										subsetCols.and(colMap);
										if (subsetCols.anySetBit() != -1)
										{
											interferent = true;
											((TableDescriptor) p).setReferencedColumnMap(subsetCols);
										}
									}
								}	// if provider instanceof TableDescriptor
							}	// for providerInfos
							if (! interferent)
								continue;
						}	// if dep instanceof ViewDescriptor
						else
							((TableDescriptor) p).setReferencedColumnMap(null);
					}	// if providingCols == null
					else
					{
						System.arraycopy(affectedCols.getByteArray(), 0, subsetCols.getByteArray(), 0, affectedCols.getLengthInBytes());
						subsetCols.and(providingCols);
						if (subsetCols.anySetBit() == -1)
							continue;
						((TableDescriptor) p).setReferencedColumnMap(subsetCols);
					}
				}

				// generate a list of invalidations that fail.
				try {
					dep.prepareToInvalidate(p, action, lcc);
				} catch (StandardException sqle) {

					if (noInvalidate == null) {
						noInvalidate = sqle;
					} else {
						try {
							sqle.initCause(noInvalidate);
							noInvalidate = sqle;
						} catch (IllegalStateException ise) {
							// We weren't able to chain the exceptions. That's
							// OK, since we always have the first exception we
							// caught. Just skip the current exception.
						}
					}
				}
				if (noInvalidate == null) {

					if (affectedCols != null)
						((TableDescriptor) p).setReferencedColumnMap(affectedCols);

					// REVISIT: future impl will want to mark the individual
					// dependency as invalid as well as the dependent...
					dep.makeInvalid(action, lcc);
				}
			}

			if (noInvalidate != null)
				throw noInvalidate;
		}
	}

	/**
		Erases all of the dependencies the dependent has, be they
		valid or invalid, of any dependency type.  This action is
		usually performed as the first step in revalidating a
		dependent; it first erases all the old dependencies, then
		revalidates itself generating a list of new dependencies,
		and then marks itself valid if all its new dependencies are
		valid.
		<p>
		There might be a future want to clear all dependencies for
		a particular provider, e.g. when destroying the provider.
		However, at present, they are assumed to stick around and
		it is the responsibility of the dependent to erase them when
		revalidating against the new version of the provider.
		<p>
		clearDependencies will delete dependencies if they are
		stored; the delete is finalized at the next commit.

		@param d the dependent
	 *
	 * @exception StandardException		Thrown on failure
	 */
	public void clearDependencies(LanguageConnectionContext lcc, Dependent d) throws StandardException {
		clearDependencies(lcc, d, null);
	}

	/**
	 * @inheritDoc
	 */
	public void clearDependencies(LanguageConnectionContext lcc, 
									Dependent d, TransactionController tc) throws StandardException {

        UUID id = d.getObjectID();
        // Remove all the stored dependencies.
        if (d.isPersistent()) {
            boolean wait = (tc == null);
            dd.dropDependentsStoredDependencies(id,
                            ((wait)?lcc.getTransactionExecute():tc),
                            wait);
        }

        // Now remove the in-memory dependencies, if any.
        synchronized(this) {
            List deps = (List) dependents.get(id);
            if (deps != null) {
                Iterator depsIter = deps.iterator();
                // go through the list notifying providers to remove
                // the dependency from their lists
                while (depsIter.hasNext()) {
                    Dependency dy = (Dependency)depsIter.next();
                    clearProviderDependency(dy.getProviderKey(), dy);
                }
                dependents.remove(id);
            }
        }
    }

	/**
	 * Clear the specified in memory dependency.
	 * This is useful for clean-up when an exception occurs.
	 * (We clear all in-memory dependencies added in the current
	 * StatementContext.)
	 */
    public synchronized void clearInMemoryDependency(Dependency dy) {
        final UUID deptId = dy.getDependent().getObjectID();
        final UUID provId = dy.getProviderKey();
        List deps = (List) dependents.get(deptId);

        // NOTE - this is a NEGATIVE Sanity mode check, in sane mode we continue
        // to ensure the dependency manager is consistent.
        if (!SanityManager.DEBUG) {
            // dependency has already been removed
            if (deps == null)
                return;
        }

        List provs = (List) providers.get(provId);

        if (SanityManager.DEBUG)
        {
            // if both are null then everything is OK
            if ((deps != null) || (provs != null)) {

                // ensure that the Dependency dy is either
                // in both lists or in neither. Even if dy
                // is out of the list we can have non-null
                // deps and provs here because other dependencies
                // with the the same providers or dependents can exist

                //
                int depCount = 0;
                if (deps != null) {
                    for (int ci = 0; ci < deps.size(); ci++) {
                        if (dy.equals(deps.get(ci)))
                            depCount++;
                    }
                }

                int provCount = 0;
                if (provs != null) {
                    for (int ci = 0; ci < provs.size(); ci++) {
                        if (dy.equals(provs.get(ci)))
                            provCount++;
                    }
                }

                SanityManager.ASSERT(depCount == provCount,
                        "Dependency count mismatch count in deps: " + depCount +
                        ", count in provs " + provCount +
                        ", dy.getDependent().getObjectID() = " + deptId +
                        ", dy.getProvider().getObjectID() = " + provId);
            }

            // dependency has already been removed,
            // matches code that is protected by !DEBUG above
            if (deps == null)
                return;
        }

        // dependency has already been removed
        if (provs == null)
            return;


        deps.remove(dy);
        if (deps.isEmpty())
            dependents.remove(deptId);
        provs.remove(dy);
        if (provs.isEmpty())
            providers.remove(provId);
    }

	/**
	 * @see DependencyManager#getPersistentProviderInfos
	 *
	 * @exception StandardException		Thrown on error
	 */
    public ProviderInfo[] getPersistentProviderInfos(Dependent dependent)
            throws StandardException {
		List list = getProviders(dependent);
        if (list.isEmpty()) {
			return EMPTY_PROVIDER_INFO;
		}

        Iterator provIter = list.iterator();
        List pih = new ArrayList();
        while (provIter.hasNext()) {
            Provider p = (Provider)provIter.next();

            if (p.isPersistent()) {
				pih.add(new BasicProviderInfo(
                                        p.getObjectID(),
                                        p.getDependableFinder(),
                                        p.getObjectName()
									));
			}
		}

		return (ProviderInfo[]) pih.toArray(EMPTY_PROVIDER_INFO);
	}

	private static final ProviderInfo[] EMPTY_PROVIDER_INFO = new ProviderInfo[0];

	/**
	 * @see DependencyManager#getPersistentProviderInfos
	 *
	 * @exception StandardException		Thrown on error
	 */
	public ProviderInfo[] getPersistentProviderInfos(ProviderList pl)
							throws StandardException
	{
		Enumeration e = pl.elements();
		int			numProviders = 0;
		ProviderInfo[]	retval;

		/*
		** We make 2 passes - the first to count the number of persistent
 		** providers and the second to populate the array of ProviderInfos.
		*/
		while (e != null && e.hasMoreElements())
		{
			Provider prov = (Provider) e.nextElement();

			if (prov.isPersistent())
			{
				numProviders++;
			}
		}

		e = pl.elements();
		retval = new ProviderInfo[numProviders];
		int piCtr = 0;
		while (e != null && e.hasMoreElements())
		{
			Provider prov = (Provider) e.nextElement();

			if (prov.isPersistent())
			{
				retval[piCtr++] = new BasicProviderInfo(
									prov.getObjectID(),
									prov.getDependableFinder(),
									prov.getObjectName()
									);
			}
		}

		return retval;
	}

	/**
	 * @see DependencyManager#clearColumnInfoInProviders
	 *
	 * @param pl		provider list
	 *
	 * @exception StandardException		Thrown on error
	 */
	public void clearColumnInfoInProviders(ProviderList pl)
					throws StandardException
	{
		Enumeration e = pl.elements();
		while (e.hasMoreElements())
		{
			Provider pro = (Provider) e.nextElement();
			if (pro instanceof TableDescriptor)
				((TableDescriptor) pro).setReferencedColumnMap(null);
		}
	}

	/**
 	 * Copy dependencies from one dependent to another.
	 *
	 * @param copy_From the dependent to copy from	
	 * @param copyTo the dependent to copy to
	 * @param persistentOnly only copy persistent dependencies
	 * @param cm		Current ContextManager
	 *
	 * @exception StandardException		Thrown on error.
	 */
	public void copyDependencies(Dependent	copy_From, 
								Dependent	copyTo,
								boolean		persistentOnly,
								ContextManager cm) throws StandardException
	{
		copyDependencies(copy_From, copyTo, persistentOnly, cm, null);
	}
	
	/**
	 * @inheritDoc 
	 */
    public void copyDependencies(
									Dependent	copy_From, 
									Dependent	copyTo,
									boolean		persistentOnly,
									ContextManager cm,
									TransactionController tc)
		throws StandardException
	{

		List list = getProviders(copy_From);
        Iterator depsIter = list.iterator();
        while (depsIter.hasNext()) {
            Provider provider = (Provider)depsIter.next();
			if (!persistentOnly || provider.isPersistent())
			{
				this.addDependency(copyTo, provider, cm, tc);
			}
		}
	}

					
	/**
	 * Returns a string representation of the SQL action, hence no
	 * need to internationalize, which is causing the invokation
	 * of the Dependency Manager.
	 *
	 * @param action		The action
	 *
	 * @return String	The String representation
	 */
	public String getActionString(int action)
	{
		switch (action)
		{
			case ALTER_TABLE:
				return "ALTER TABLE";

			case RENAME: //for rename table and column
				return "RENAME";

			case RENAME_INDEX:
				return "RENAME INDEX";

			case COMPILE_FAILED:
				return "COMPILE FAILED";

			case DROP_TABLE:
				return "DROP TABLE";

			case DROP_INDEX:
				return "DROP INDEX";

			case DROP_VIEW:
				return "DROP VIEW";

			case CREATE_INDEX:
				return "CREATE INDEX";

			case ROLLBACK:
				return "ROLLBACK";

			case CHANGED_CURSOR:
				return "CHANGED CURSOR";

			case CREATE_CONSTRAINT:
				return "CREATE CONSTRAINT";

			case DROP_CONSTRAINT:
				return "DROP CONSTRAINT";

			case DROP_METHOD_ALIAS:
				return "DROP ROUTINE";

			case PREPARED_STATEMENT_RELEASE:
				return "PREPARED STATEMENT RELEASE";

			case DROP_SPS:
				return "DROP STORED PREPARED STATEMENT";

			case USER_RECOMPILE_REQUEST:
				return "USER REQUESTED INVALIDATION";

			case BULK_INSERT:
				return "BULK INSERT";

		    case CREATE_VIEW:
				return "CREATE_VIEW";
 
			case DROP_JAR:
				return "DROP_JAR";

			case REPLACE_JAR:
				return "REPLACE_JAR";

			case SET_CONSTRAINTS_ENABLE:
				return "SET_CONSTRAINTS_ENABLE";

			case SET_CONSTRAINTS_DISABLE:
				return "SET_CONSTRAINTS_DISABLE";

			case INTERNAL_RECOMPILE_REQUEST:
				return "INTERNAL RECOMPILE REQUEST";

			case CREATE_TRIGGER:
				return "CREATE TRIGGER";

			case DROP_TRIGGER:
				return "DROP TRIGGER";

			case SET_TRIGGERS_ENABLE:
				return "SET TRIGGERS ENABLED";

			case SET_TRIGGERS_DISABLE:
				return "SET TRIGGERS DISABLED";

			case MODIFY_COLUMN_DEFAULT:
				return "MODIFY COLUMN DEFAULT";

			case COMPRESS_TABLE:
				return "COMPRESS TABLE";

			case DROP_COLUMN:
				return "DROP COLUMN";

			case DROP_COLUMN_RESTRICT:
				return "DROP COLUMN RESTRICT";

		    case DROP_STATISTICS:
				return "DROP STATISTICS";

			case UPDATE_STATISTICS:
				return "UPDATE STATISTICS";

		    case TRUNCATE_TABLE:
			    return "TRUNCATE TABLE";

		    case DROP_SYNONYM:
			    return "DROP SYNONYM";

		    case REVOKE_PRIVILEGE:
			    return "REVOKE PRIVILEGE";

		    case REVOKE_PRIVILEGE_RESTRICT:
			    return "REVOKE PRIVILEGE RESTRICT";

		    case REVOKE_ROLE:
				return "REVOKE ROLE";

		    case RECHECK_PRIVILEGES:
				return "RECHECK PRIVILEGES";

            case DROP_SEQUENCE:
				return "DROP SEQUENCE";

            case DROP_UDT:
				return "DROP TYPE";

            case DROP_AGGREGATE:
				return "DROP DERBY AGGREGATE";

            default:
				if (SanityManager.DEBUG)
				{
					SanityManager.THROWASSERT("getActionString() passed an invalid value (" + action + ")");
				}
				// NOTE: This is not internationalized because we should never
				// reach here.
				return "UNKNOWN";
		}
	}

	/**
	 * Count the number of active dependencies, both stored and in memory,
	 * in the system.
	 *
	 * @return int		The number of active dependencies in the system.

		@exception StandardException thrown if something goes wrong
	 */
	public int countDependencies()
		throws StandardException
	{
        // Add the stored dependencies.
        List storedDeps = dd.getAllDependencyDescriptorsList();
        int numDependencies = storedDeps.size();
        synchronized(this) {
            Iterator deps = dependents.values().iterator();
            Iterator provs = providers.values().iterator();

            // Count the in memory dependencies.
            while (deps.hasNext()) {
                numDependencies += ((List) deps.next()).size();
            }

            while (provs.hasNext()) {
                numDependencies += ((List) provs.next()).size();
            }
        }
        return numDependencies;
	}

	//
	// class interface
	//
	public BasicDependencyManager(DataDictionary dd) {
        this.dd = dd;
	}

	//
	// class implementation
	//

	/**
	 * Add a new dependency to the specified table if it does not
	 * already exist in that table.
	 *
	 * @return boolean		Whether or not the dependency get added.
	 */
    private boolean addDependencyToTable(Map table, Object key, Dependency dy) {

		List deps = (List) table.get(key);
		if (deps == null) {
            deps = new ArrayList();
			deps.add(dy);
			table.put(key, deps);
		}
		else {
			/* Make sure that we're not adding a duplicate dependency */
			UUID	provKey = dy.getProvider().getObjectID();
			UUID	depKey = dy.getDependent().getObjectID();

			for (ListIterator depsIT = deps.listIterator();  depsIT.hasNext(); )
			{
				Dependency curDY = (Dependency)depsIT.next();
				if (curDY.getProvider().getObjectID().equals(provKey) &&
					curDY.getDependent().getObjectID().equals(depKey))
				{
					return false;
				}
			}

			deps.add(dy);
		}

		if (SanityManager.DEBUG) {

			if (SanityManager.DEBUG_ON("memoryLeakTrace")) {

				if (table.size() > 100)
					System.out.println("memoryLeakTrace:BasicDependencyManager:table " + table.size());
				if (deps.size() > 50)
					System.out.println("memoryLeakTrace:BasicDependencyManager:deps " + deps.size());
			}
		}

		return true;
	}

	/**
	 * removes a dependency for a given provider. assumes
	 * that the dependent removal is being dealt with elsewhere.
	 * Won't assume that the dependent only appears once in the list.
	 */
    //@GuardedBy("this")
    private void clearProviderDependency(UUID p, Dependency d) {
		List deps = (List) providers.get(p);

		if (deps == null)
			return;

		deps.remove(d);

		if (deps.size() == 0)
			providers.remove(p);
	}

	/**
	 * Replace the DependencyDescriptors in an List with Dependencys.
	 *
	 * @param storedList	The List of DependencyDescriptors representing
	 *						stored dependencies.
	 * @param providerForList The provider if this list is being created
	 *                        for a list of dependents. Null otherwise.
	 * 
	 * @return List		The converted List
	 *
	 * @exception StandardException thrown if something goes wrong
	 */
	private List getDependencyDescriptorList(List storedList,
			Provider providerForList)
		throws StandardException
	{
		if (storedList.size() != 0)
		{
			/* For each DependencyDescriptor, we need to instantiate
			 * object descriptors of the appropriate type for both
			 * the dependent and provider, create a Dependency with
			 * that Dependent and Provider and substitute the Dependency
			 * back into the same place in the List
			 * so that the call gets an enumerations of Dependencys.
			 */
			for (ListIterator depsIterator = storedList.listIterator();
				 depsIterator.hasNext(); ) 
			{
				Dependent 			tempD;
				Provider  			tempP;
				DependableFinder	finder = null;

				DependencyDescriptor depDesc = (DependencyDescriptor) depsIterator.next();

					finder = depDesc.getDependentFinder();
					tempD = (Dependent) finder.getDependable(dd, depDesc.getUUID() );

					if (providerForList != null)
					{
						// Use the provider being passed in.
						tempP = providerForList;
						
						// Sanity check the object identifiers match.
						if (SanityManager.DEBUG) {
							if (!tempP.getObjectID().equals(depDesc.getProviderID()))
							{
								SanityManager.THROWASSERT("mismatch providers");
							}
						}
					}
					else
					{
						finder = depDesc.getProviderFinder();
						tempP = (Provider) finder.getDependable(dd, depDesc.getProviderID() );
						
					}

				depsIterator.set(new BasicDependency(tempD, tempP));
			}
		}

		return storedList;
	}

	/**
	 * Returns the LanguageConnectionContext to use.
	 *
	 * @param cm	Current ContextManager
	 *
	 * @return LanguageConnectionContext	The LanguageConnectionContext to use.
	 */
	private LanguageConnectionContext getLanguageConnectionContext(ContextManager cm)
	{
		// find the language context.
		return (LanguageConnectionContext) cm.getContext(LanguageConnectionContext.CONTEXT_ID);
	}

	/**
     * Returns a list of all providers that this dependent has (even invalid
     * ones). Includes all dependency types.
     *
     * @param d the dependent
     * @return A list of providers (possibly empty).
     * @throws StandardException thrown if something goes wrong
     */
    private List getProviders (Dependent d) throws StandardException {
        List provs = new ArrayList();
        synchronized (this) {
            List deps = (List) dependents.get(d.getObjectID());
            if (deps != null) {
                Iterator depsIter = deps.iterator();
                while (depsIter.hasNext()) {
                    provs.add(((Dependency)depsIter.next()).getProvider());
                }
            }
        }

        // If the dependent is persistent, we have to take stored dependencies
        // into consideration as well.
        if (d.isPersistent()) {
			List storedList = getDependencyDescriptorList(
								dd.getDependentsDescriptorList(
												d.getObjectID().toString()
															),
								(Provider) null
													);
            Iterator depIter = storedList.iterator();
            while (depIter.hasNext()) {
                provs.add(((Dependency)depIter.next()).getProvider());
            }
		}
        return provs;
	}

    /**
     * Returns an enumeration of all dependencies that this
     * provider is supporting for any dependent at all (even
     * invalid ones). Includes all dependency types.
     *
     * @param p the provider
     * @return A list of dependents (possibly empty).
     * @throws StandardException if something goes wrong
	 */
	private List getDependents (Provider p) 
			throws StandardException {
        List deps = new ArrayList();
        synchronized (this) {
            List memDeps = (List) providers.get(p.getObjectID());
            if (memDeps != null) {
                deps.addAll(memDeps);
            }
        }

        // If the provider is persistent, then we have to add providers for
        // stored dependencies as well.
        if (p.isPersistent()) {
			List storedList = getDependencyDescriptorList(
								dd.getProvidersDescriptorList(
												p.getObjectID().toString()
															),
							p
													);
            deps.addAll(storedList);
        }
        return deps;
	}
}
