/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Mike McCabe <mccabe@netscape.com>
 *   John Bandhauer <jband@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/* Implementation of xptiInterfaceInfoManager. */

#include "xptiprivate.h"
#include "nsDependentString.h"
#include "nsString.h"
#include "nsArrayEnumerator.h"

#define NS_ZIPLOADER_CONTRACTID NS_XPTLOADER_CONTRACTID_PREFIX "zip"

NS_IMPL_THREADSAFE_ISUPPORTS2(xptiInterfaceInfoManager, 
                              nsIInterfaceInfoManager,
                              nsIInterfaceInfoSuperManager)

static xptiInterfaceInfoManager* gInterfaceInfoManager = nsnull;
#ifdef DEBUG
static int gCallCount = 0;
#endif

// static
xptiInterfaceInfoManager*
xptiInterfaceInfoManager::GetInterfaceInfoManagerNoAddRef()
{
    if(!gInterfaceInfoManager)
    {
        nsCOMPtr<nsISupportsArray> searchPath;
        BuildFileSearchPath(getter_AddRefs(searchPath));
        if(!searchPath)
        {
            NS_ERROR("can't get xpt search path!");    
            return nsnull;
        }

        gInterfaceInfoManager = new xptiInterfaceInfoManager(searchPath);
        if(!gInterfaceInfoManager)
        {
            NS_ERROR("can't instantiate xptiInterfaceInfoManager");
            return nsnull;
        }

        NS_ADDREF(gInterfaceInfoManager);

        if(!gInterfaceInfoManager->IsValid())
        {
            NS_RELEASE(gInterfaceInfoManager);
        }
        else
        {
            PRBool mustAutoReg = 
                    !xptiManifest::Read(gInterfaceInfoManager, 
                                        &gInterfaceInfoManager->mWorkingSet);
#ifdef DEBUG
            {
            // This sets what will be returned by GetOpenLogFile().
            xptiAutoLog autoLog(gInterfaceInfoManager, 
                                gInterfaceInfoManager->mAutoRegLogFile, PR_TRUE);
            LOG_AUTOREG(("debug build forced autoreg after %s load of manifest\n", mustAutoReg ? "FAILED" : "successful"));
        
            mustAutoReg = PR_TRUE;
            }
#endif // DEBUG
            if(mustAutoReg)
                gInterfaceInfoManager->AutoRegisterInterfaces();
        }
    }
    return gInterfaceInfoManager;
}

void
xptiInterfaceInfoManager::FreeInterfaceInfoManager()
{
    if(gInterfaceInfoManager)
        gInterfaceInfoManager->LogStats();

    NS_IF_RELEASE(gInterfaceInfoManager);
}

PRBool 
xptiInterfaceInfoManager::IsValid()
{
    return mWorkingSet.IsValid() &&
           mResolveLock &&
           mAutoRegLock &&
           mInfoMonitor &&
           mAdditionalManagersLock;
}        

xptiInterfaceInfoManager::xptiInterfaceInfoManager(nsISupportsArray* aSearchPath)
    :   mWorkingSet(aSearchPath),
        mOpenLogFile(nsnull),
        mResolveLock(PR_NewLock()),
        mAutoRegLock(PR_NewLock()),
        mInfoMonitor(nsAutoMonitor::NewMonitor("xptiInfoMonitor")),
        mAdditionalManagersLock(PR_NewLock()),
        mSearchPath(aSearchPath)
{
    const char* statsFilename = PR_GetEnv("MOZILLA_XPTI_STATS");
    if(statsFilename && *statsFilename)
    {
        mStatsLogFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);         
        if(mStatsLogFile && 
           NS_SUCCEEDED(mStatsLogFile->InitWithNativePath(nsDependentCString(statsFilename))))
        {
            printf("* Logging xptinfo stats to: %s\n", statsFilename);
        }
        else
        {
            printf("* Failed to create xptinfo stats file: %s\n", statsFilename);
            mStatsLogFile = nsnull;
        }
    }

    const char* autoRegFilename = PR_GetEnv("MOZILLA_XPTI_REGLOG");
    if(autoRegFilename && *autoRegFilename)
    {
        mAutoRegLogFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);         
        if(mAutoRegLogFile && 
           NS_SUCCEEDED(mAutoRegLogFile->InitWithNativePath(nsDependentCString(autoRegFilename))))
        {
            printf("* Logging xptinfo autoreg to: %s\n", autoRegFilename);
        }
        else
        {
            printf("* Failed to create xptinfo autoreg file: %s\n", autoRegFilename);
            mAutoRegLogFile = nsnull;
        }
    }
}

xptiInterfaceInfoManager::~xptiInterfaceInfoManager()
{
    // We only do this on shutdown of the service.
    mWorkingSet.InvalidateInterfaceInfos();

    if(mResolveLock)
        PR_DestroyLock(mResolveLock);
    if(mAutoRegLock)
        PR_DestroyLock(mAutoRegLock);
    if(mInfoMonitor)
        nsAutoMonitor::DestroyMonitor(mInfoMonitor);
    if(mAdditionalManagersLock)
        PR_DestroyLock(mAdditionalManagersLock);

    gInterfaceInfoManager = nsnull;
#ifdef DEBUG
    xptiInterfaceInfo::DEBUG_ShutdownNotification();
    gCallCount = 0;
#endif
}

static nsresult
GetDirectoryFromDirService(const char* codename, nsILocalFile** aDir)
{
    NS_ASSERTION(codename,"loser!");
    NS_ASSERTION(aDir,"loser!");
    
    nsresult rv;
    nsCOMPtr<nsIProperties> dirService =
        do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
    if (NS_FAILED(rv)) return rv;

    return dirService->Get(codename, NS_GET_IID(nsILocalFile), (void**) aDir);
}

static PRBool
AppendFromDirServiceList(const char* codename, nsISupportsArray* aPath)
{
    NS_ASSERTION(codename,"loser!");
    NS_ASSERTION(aPath,"loser!");
    
    nsCOMPtr<nsIProperties> dirService = 
        do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
    if(!dirService)
        return PR_FALSE;

    nsCOMPtr<nsISimpleEnumerator> fileList;
    dirService->Get(codename, NS_GET_IID(nsISimpleEnumerator), 
                    getter_AddRefs(fileList));
    if(!fileList)
        return PR_FALSE;
    
    PRBool more;
    while(NS_SUCCEEDED(fileList->HasMoreElements(&more)) && more)
    {
        nsCOMPtr<nsILocalFile> dir;
        fileList->GetNext(getter_AddRefs(dir));
        if(!dir || !aPath->AppendElement(dir))
            return PR_FALSE;
    }

    return PR_TRUE;
}        

// static 
PRBool xptiInterfaceInfoManager::BuildFileSearchPath(nsISupportsArray** aPath)
{
#ifdef DEBUG
    NS_ASSERTION(!gCallCount++, "Expected only one call!");
#endif

    nsCOMPtr<nsISupportsArray> searchPath;
    NS_NewISupportsArray(getter_AddRefs(searchPath));
    if(!searchPath)
        return PR_FALSE;
    
    nsCOMPtr<nsILocalFile> compDir;

    // Always put components directory first

    if(NS_FAILED(GetDirectoryFromDirService(NS_XPCOM_COMPONENT_DIR, 
                                            getter_AddRefs(compDir))) ||
       !searchPath->AppendElement(compDir))
    {
        return PR_FALSE;
    }

    // No error checking here since this is optional in some embeddings
    
    // Add the GRE's component directory to searchPath if the 
    // application is using an GRE.
    // An application indicates that it's using an GRE by returning
    // a valid nsIFile via its directory service provider interface.
    //
    // Please see http://www.mozilla.org/projects/embedding/MRE.html
    // for more info. on GREs
    //
    nsCOMPtr<nsILocalFile> greComponentDirectory;
    nsresult rv = GetDirectoryFromDirService(NS_GRE_COMPONENT_DIR, 
                                    getter_AddRefs(greComponentDirectory));
    if(NS_SUCCEEDED(rv) && greComponentDirectory)
    {
        // make sure we only append a directory if its a different one
        PRBool equalsCompDir = PR_FALSE;
        greComponentDirectory->Equals(compDir, &equalsCompDir);

        if(!equalsCompDir)
            searchPath->AppendElement(greComponentDirectory);
    }

    (void)AppendFromDirServiceList(NS_XPCOM_COMPONENT_DIR_LIST, searchPath);

    NS_ADDREF(*aPath = searchPath);
    return PR_TRUE;
}

PRBool 
xptiInterfaceInfoManager::GetCloneOfManifestLocation(nsILocalFile** aFile)
{
    // We *trust* that this will not change!
    nsCOMPtr<nsILocalFile> lf;
    nsresult rv = GetDirectoryFromDirService(NS_XPCOM_XPTI_REGISTRY_FILE, 
                                             getter_AddRefs(lf));

    if (NS_FAILED(rv)) return PR_FALSE;

    rv = xptiCloneLocalFile(lf, aFile);
    if (NS_FAILED(rv)) return PR_FALSE;
    return PR_TRUE;
}

PRBool 
xptiInterfaceInfoManager::GetApplicationDir(nsILocalFile** aDir)
{
    // We *trust* that this will not change!
    return NS_SUCCEEDED(GetDirectoryFromDirService(NS_XPCOM_CURRENT_PROCESS_DIR, aDir));
}

PRBool 
xptiInterfaceInfoManager::BuildFileList(nsISupportsArray* aSearchPath,
                                        nsISupportsArray** aFileList)
{
    NS_ASSERTION(aFileList, "loser!");
    
    nsresult rv;

    nsCOMPtr<nsISupportsArray> fileList = 
        do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID);
    if(!fileList)
        return PR_FALSE;

    PRUint32 pathCount;
    if(NS_FAILED(aSearchPath->Count(&pathCount)))
        return PR_FALSE;

    nsCOMPtr<nsILocalFile> dir;
    nsCOMPtr<nsISimpleEnumerator> entries;
    nsCOMPtr<nsISupports> sup;
    nsCOMPtr<nsILocalFile> file;

    // Iterate the paths backwards to avoid the
    // insertions that would occur if we preserved
    // the order of the list.
    for(PRUint32 i = pathCount; i; i--)
    {
        rv = xptiCloneElementAsLocalFile(aSearchPath, (i - 1), getter_AddRefs(dir));
        if(NS_FAILED(rv) || !dir)
            return PR_FALSE;

        rv = dir->GetDirectoryEntries(getter_AddRefs(entries));
        if(NS_FAILED(rv) || !entries)
            continue;

        PRBool hasMore;
        while(NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore)
        {
            entries->GetNext(getter_AddRefs(sup));
            if(!sup)
                return PR_FALSE;

            file = do_QueryInterface(sup);
            if(!file)
                return PR_FALSE;

            PRBool isFile;
            if(NS_FAILED(file->IsFile(&isFile)) || !isFile)
            {
                continue;
            }
     
            nsCAutoString name;
            if(NS_FAILED(file->GetNativeLeafName(name)))
                return PR_FALSE;

            if(xptiFileType::IsUnknown(name.get()))
                continue;

            LOG_AUTOREG(("found file: %s\n", name.get()));

            if(!fileList->AppendElement(file))
                return PR_FALSE;
        }
    }

    fileList.swap(*aFileList);
    return PR_TRUE;
}

XPTHeader* 
xptiInterfaceInfoManager::ReadXPTFile(nsILocalFile* aFile, 
                                      xptiWorkingSet* aWorkingSet)
{
    NS_ASSERTION(aFile, "loser!");

    XPTHeader *header = nsnull;
    char *whole = nsnull;
    PRFileDesc*   fd = nsnull;
    XPTState *state = nsnull;
    XPTCursor cursor;
    PRInt32 flen;
    PRInt64 fileSize;
    
    PRBool saveFollowLinks;
    aFile->GetFollowLinks(&saveFollowLinks);
    aFile->SetFollowLinks(PR_TRUE);

    if(NS_FAILED(aFile->GetFileSize(&fileSize)) || !(flen = nsInt64(fileSize)))
    {
        aFile->SetFollowLinks(saveFollowLinks);
        return nsnull;
    }

    whole = new char[flen];
    if (!whole)
    {
        aFile->SetFollowLinks(saveFollowLinks);
        return nsnull;
    }

    // all exits from on here should be via 'goto out' 

    if(NS_FAILED(aFile->OpenNSPRFileDesc(PR_RDONLY, 0444, &fd)) || !fd)
    {
        goto out;
    }

    if(flen > PR_Read(fd, whole, flen))
    {
        goto out;
    }

    if(!(state = XPT_NewXDRState(XPT_DECODE, whole, flen)))
    {
        goto out;
    }
    
    if(!XPT_MakeCursor(state, XPT_HEADER, 0, &cursor))
    {
        goto out;
    }
    
    if (!XPT_DoHeader(aWorkingSet->GetStructArena(), &cursor, &header))
    {
        header = nsnull;
        goto out;
    }

 out:
    if(fd)
        PR_Close(fd);
    if(state)
        XPT_DestroyXDRState(state);
    if(whole)
        delete [] whole;
    aFile->SetFollowLinks(saveFollowLinks);
    return header;
}

PRBool 
xptiInterfaceInfoManager::LoadFile(const xptiTypelib& aTypelibRecord,
                                 xptiWorkingSet* aWorkingSet)
{
    if(!aWorkingSet)
        aWorkingSet = &mWorkingSet;

    if(!aWorkingSet->IsValid())
        return PR_FALSE;

    xptiFile* fileRecord = &aWorkingSet->GetFileAt(aTypelibRecord.GetFileIndex());
    xptiZipItem* zipItem = nsnull;

    nsCOMPtr<nsILocalFile> file;
    if(NS_FAILED(aWorkingSet->GetCloneOfDirectoryAt(fileRecord->GetDirectory(),
                                    getter_AddRefs(file))) || !file)
        return PR_FALSE;

    if(NS_FAILED(file->AppendNative(nsDependentCString(fileRecord->GetName()))))
        return PR_FALSE;

    XPTHeader* header;

    if(aTypelibRecord.IsZip())
    {
        zipItem = &aWorkingSet->GetZipItemAt(aTypelibRecord.GetZipItemIndex());
        
        // See the big comment below in the 'non-zip' case...
        if(zipItem->GetGuts())
        {
            NS_ERROR("Trying to load an xpt file from a zip twice");    
            
            // Force an autoreg on next run
            (void) xptiManifest::Delete(this);

            return PR_FALSE;
        }
        
        LOG_LOAD(("# loading zip item %s::%s\n", fileRecord->GetName(), zipItem->GetName()));
        
        nsCOMPtr<nsIXPTLoader> loader =
            do_GetService(NS_ZIPLOADER_CONTRACTID);

        if (loader) {
            nsresult rv;
            
            nsCOMPtr<nsIInputStream> stream;
            rv = loader->LoadEntry(file, zipItem->GetName(),
                                   getter_AddRefs(stream));

            if (NS_FAILED(rv))
                return PR_FALSE;
            
            header =
                xptiZipLoader::ReadXPTFileFromInputStream(stream, aWorkingSet);
        } else {
            header = nsnull;
            NS_WARNING("Could not load XPT Zip loader");
        }
    } 
    else
    {
        // The file would only have guts already if we previously failed to 
        // find an interface info in a file where the manifest claimed it was 
        // going to be. 
        //
        // Normally, when the file gets loaded (and the guts set) then all 
        // interfaces would also be resolved. So, if we are here again for
        // the same file then there must have been some interface that was 
        // expected but not present. Now we are explicitly trying to find it
        // and it isn't going to be there this time either.
        //
        // This is an assertion style error in a DEBUG build because it shows 
        // that we failed to detect this in autoreg. For release builds (where
        // autoreg is not run on every startup) it is just bad. But by returning
        // PR_FALSE we mark this interface as RESOLVE_FAILED and get on with 
        // things without crashing or anything.
        //
        // We don't want to do an autoreg here because this is too much of an 
        // edge case (and in that odd case it might autoreg multiple times if
        // many interfaces had been removed). But, by deleting the manifest we 
        // force the system to get it right on the next run.
        
        if(fileRecord->GetGuts())
        {
            NS_ERROR("Trying to load an xpt file twice");    
            
            // Force an autoreg on next run
            (void) xptiManifest::Delete(this);

            return PR_FALSE;
        }

        LOG_LOAD(("^ loading file %s\n", fileRecord->GetName()));
        header = ReadXPTFile(file, aWorkingSet);
    } 

    if(!header)
        return PR_FALSE;


    if(aTypelibRecord.IsZip())
    {
        // This also allocs zipItem.GetGuts() used below.
        if(!zipItem->SetHeader(header, aWorkingSet))
            return PR_FALSE;
    }
    else
    {
        // This also allocs fileRecord.GetGuts() used below.
        if(!fileRecord->SetHeader(header, aWorkingSet))
            return PR_FALSE;
    }

    // For each interface in the header we want to find the xptiInterfaceInfo
    // object and set its resolution info.

    for(PRUint16 i = 0; i < header->num_interfaces; i++)
    {
        static const nsID zeroIID =
            { 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } };

        XPTInterfaceDirectoryEntry* iface = header->interface_directory + i;
        
        xptiHashEntry* hashEntry;

        if(!iface->iid.Equals(zeroIID))
        {
            hashEntry = (xptiHashEntry*)
                PL_DHashTableOperate(aWorkingSet->mIIDTable, 
                                     &iface->iid, PL_DHASH_LOOKUP);
        }
        else
        {
            hashEntry = (xptiHashEntry*)
                PL_DHashTableOperate(aWorkingSet->mNameTable, 
                                     iface->name, PL_DHASH_LOOKUP);
        }

        xptiInterfaceEntry* entry = 
            PL_DHASH_ENTRY_IS_FREE(hashEntry) ? nsnull : hashEntry->value;

        if(!entry)
        {
            // This one is just not resolved anywhere!
            continue;    
        }

        if(aTypelibRecord.IsZip())
            zipItem->GetGuts()->SetEntryAt(i, entry);
        else
            fileRecord->GetGuts()->SetEntryAt(i, entry);

        XPTInterfaceDescriptor* descriptor = iface->interface_descriptor;

        if(descriptor && aTypelibRecord.Equals(entry->GetTypelibRecord()))
            entry->PartiallyResolveLocked(descriptor, aWorkingSet);
    }
    return PR_TRUE;
}

static int
IndexOfFileWithName(const char* aName, const xptiWorkingSet* aWorkingSet)
{
    NS_ASSERTION(aName, "loser!");

    for(PRUint32 i = 0; i < aWorkingSet->GetFileCount(); ++i)
    {
        if(0 == PL_strcmp(aName, aWorkingSet->GetFileAt(i).GetName()))
            return i;     
    }
    return -1;        
}        

static int
IndexOfDirectoryOfFile(nsISupportsArray* aSearchPath, nsILocalFile* aFile)
{
    nsCOMPtr<nsIFile> parent;
    aFile->GetParent(getter_AddRefs(parent));
    if(parent)
    {
        PRUint32 count = 0;
        aSearchPath->Count(&count);
        NS_ASSERTION(count, "broken search path! bad count");
        for(PRUint32 i = 0; i < count; i++)
        {
            nsCOMPtr<nsIFile> current;
            aSearchPath->QueryElementAt(i, NS_GET_IID(nsIFile), 
                                        getter_AddRefs(current));
            NS_ASSERTION(current, "broken search path! bad element");
            // nsIFile::Equals basically compares path strings so normalize
            // before the comparison.
            parent->Normalize();
            current->Normalize();
            PRBool same;
            if (NS_SUCCEEDED(parent->Equals(current, &same)) && same)
                return (int) i;
        }
    }
    NS_ERROR("file not in search directory!");
    return -1;
}        

struct SortData
{
    nsISupportsArray* mSearchPath;
    xptiWorkingSet*   mWorkingSet;
};

static int
xptiSortFileList(const void * p1, const void *p2, void * closure)
{
    nsILocalFile* pFile1 = *((nsILocalFile**) p1);
    nsILocalFile* pFile2 = *((nsILocalFile**) p2);
    SortData* data = (SortData*) closure;
    
    nsCAutoString name1;
    nsCAutoString name2;
        
    if(NS_FAILED(pFile1->GetNativeLeafName(name1)))
    {
        NS_ERROR("way bad, with no happy out!");
        return 0;    
    }    
    if(NS_FAILED(pFile2->GetNativeLeafName(name2)))
    {
        NS_ERROR("way bad, with no happy out!");
        return 0;    
    }    

    int index1 = IndexOfFileWithName(name1.get(), data->mWorkingSet); 
    int index2 = IndexOfFileWithName(name2.get(), data->mWorkingSet); 
   
    // Get these now in case we need them later.
    PRBool isXPT1 = xptiFileType::IsXPT(name1.get());
    PRBool isXPT2 = xptiFileType::IsXPT(name2.get());
    int nameOrder = Compare(name1, name2);
    
    // both in workingSet, preserve old order
    if(index1 != -1 && index2 != -1)
        return index1 - index2;

    if(index1 != -1)
        return 1;

    if(index2 != -1)
        return -1;

    // neither is in workingset

    // check how they compare in search path order
    
    int dirIndex1 = IndexOfDirectoryOfFile(data->mSearchPath, pFile1);
    int dirIndex2 = IndexOfDirectoryOfFile(data->mSearchPath, pFile2);

    if(dirIndex1 != dirIndex2)
        return dirIndex1 - dirIndex2;

    // .xpt files come before archives (.zip, .jar, etc)
    if(isXPT1 &&!isXPT2)
        return -1;
        
    if(!isXPT1 && isXPT2)
        return 1;
    
    // neither element is in the workingSet and both are same type and in 
    // the same directory, sort by size

    PRInt64 size1;
    PRInt64 size2;

    if(NS_FAILED(pFile1->GetFileSize(&size1)))
    {
        NS_ERROR("way bad, with no happy out!");
        return 0;    
    }    
    if(NS_FAILED(pFile2->GetFileSize(&size2)))
    {
        NS_ERROR("way bad, with no happy out!");
        return 0;    
    }    

    // by size with largest first, or by name if size is the same
    int sizeDiff = int(PRInt32(nsInt64(size2) - nsInt64(size1)));
    return sizeDiff != 0  ? sizeDiff : nameOrder;
}        

nsILocalFile** 
xptiInterfaceInfoManager::BuildOrderedFileArray(nsISupportsArray* aSearchPath,
                                                nsISupportsArray* aFileList,
                                                xptiWorkingSet* aWorkingSet)
{
    // We want to end up with a file list that starts with the files from
    // aWorkingSet (but only those that are in aFileList) in the order in 
    // which they appeared in aWorkingSet-> Following those files will be those
    // files in aFileList which are not in aWorkingSet-> These additional
    // files will be ordered by file size (larger first) but all .xpt files
    // will preceed all zipfile of those files not already in the working set.
    // To do this we will do a fancy sort on a copy of aFileList.

    nsILocalFile** orderedFileList = nsnull;
    PRUint32 countOfFilesInFileList;
    PRUint32 i;

    NS_ASSERTION(aFileList, "loser!");
    NS_ASSERTION(aWorkingSet, "loser!");
    NS_ASSERTION(aWorkingSet->IsValid(), "loser!");

    if(NS_FAILED(aFileList->Count(&countOfFilesInFileList)) || 
       0 == countOfFilesInFileList)
        return nsnull;

    orderedFileList = (nsILocalFile**) 
        XPT_MALLOC(aWorkingSet->GetStructArena(),
                   sizeof(nsILocalFile*) * countOfFilesInFileList);
    
    if(!orderedFileList)
        return nsnull;

    // fill our list for sorting
    for(i = 0; i < countOfFilesInFileList; ++i)
    {
        nsCOMPtr<nsILocalFile> file;
        aFileList->QueryElementAt(i, NS_GET_IID(nsILocalFile), getter_AddRefs(file));
        NS_ASSERTION(file, "loser!");

        // Intentionally NOT addref'd cuz we know these are pinned in aFileList.
        orderedFileList[i] = file.get();
    }

    // sort the filelist

    SortData sortData = {aSearchPath, aWorkingSet};
    NS_QuickSort(orderedFileList, countOfFilesInFileList, sizeof(nsILocalFile*),
                 xptiSortFileList, &sortData);
     
    return orderedFileList;
}

xptiInterfaceInfoManager::AutoRegMode 
xptiInterfaceInfoManager::DetermineAutoRegStrategy(nsISupportsArray* aSearchPath,
                                                   nsISupportsArray* aFileList,
                                                   xptiWorkingSet* aWorkingSet)
{
    NS_ASSERTION(aFileList, "loser!");
    NS_ASSERTION(aWorkingSet, "loser!");
    NS_ASSERTION(aWorkingSet->IsValid(), "loser!");

    PRUint32 countOfFilesInWorkingSet = aWorkingSet->GetFileCount();
    PRUint32 countOfFilesInFileList;
    PRUint32 i;
    PRUint32 k;

    if(0 == countOfFilesInWorkingSet)
    {
        // Loading manifest might have failed. Better safe...     
        return FULL_VALIDATION_REQUIRED;
    }

    if(NS_FAILED(aFileList->Count(&countOfFilesInFileList)))
    {
        NS_ERROR("unexpected!");
        return FULL_VALIDATION_REQUIRED;
    }       

    if(countOfFilesInFileList == countOfFilesInWorkingSet)
    {
        // try to determine if *no* files are new or changed.
     
        PRBool same = PR_TRUE;
        for(i = 0; i < countOfFilesInFileList && same; ++i)
        {
            nsCOMPtr<nsILocalFile> file;
            aFileList->QueryElementAt(i, NS_GET_IID(nsILocalFile), getter_AddRefs(file));
            NS_ASSERTION(file, "loser!");

            PRInt64 size;
            PRInt64 date;
            nsCAutoString name;
            PRUint32 directory;

            if(NS_FAILED(file->GetFileSize(&size)) ||
               NS_FAILED(file->GetLastModifiedTime(&date)) ||
               NS_FAILED(file->GetNativeLeafName(name)) ||
               !aWorkingSet->FindDirectoryOfFile(file, &directory))
            {
                NS_ERROR("unexpected!");
                return FULL_VALIDATION_REQUIRED;
            }    

            for(k = 0; k < countOfFilesInWorkingSet; ++k)
            {
                xptiFile& target = aWorkingSet->GetFileAt(k);
                
                if(directory == target.GetDirectory() &&
                   name.Equals(target.GetName()))
                {
                    if(nsInt64(size) != target.GetSize() ||
                       nsInt64(date) != target.GetDate())
                        same = PR_FALSE;
                    break;        
                }
            }
            // failed to find our file in the workingset?
            if(k == countOfFilesInWorkingSet)
                same = PR_FALSE;
        }
        if(same)
            return NO_FILES_CHANGED;
    }
    else if(countOfFilesInFileList > countOfFilesInWorkingSet)
    {
        // try to determine if the only changes are additional new files
        // XXX Wimping out and doing this as a separate walk through the lists.

        PRBool same = PR_TRUE;

        for(i = 0; i < countOfFilesInWorkingSet && same; ++i)
        {
            xptiFile& target = aWorkingSet->GetFileAt(i);
            
            for(k = 0; k < countOfFilesInFileList; ++k)
            {
                nsCOMPtr<nsILocalFile> file;
                aFileList->QueryElementAt(k, NS_GET_IID(nsILocalFile), getter_AddRefs(file));
                NS_ASSERTION(file, "loser!");
                
                nsCAutoString name;
                PRInt64 size;
                PRInt64 date;
                if(NS_FAILED(file->GetFileSize(&size)) ||
                   NS_FAILED(file->GetLastModifiedTime(&date)) ||
                   NS_FAILED(file->GetNativeLeafName(name)))
                {
                    NS_ERROR("unexpected!");
                    return FULL_VALIDATION_REQUIRED;
                }    
            
                PRBool sameName = name.Equals(target.GetName());
                if(sameName)
                {
                    if(nsInt64(size) != target.GetSize() ||
                       nsInt64(date) != target.GetDate())
                        same = PR_FALSE;
                    break;        
                }
            }
            // failed to find our file in the file list?
            if(k == countOfFilesInFileList)
                same = PR_FALSE;
        }
        if(same)
            return FILES_ADDED_ONLY;
    }

    return FULL_VALIDATION_REQUIRED; 
}

PRBool 
xptiInterfaceInfoManager::AddOnlyNewFilesFromFileList(nsISupportsArray* aSearchPath,
                                                      nsISupportsArray* aFileList,
                                                      xptiWorkingSet* aWorkingSet)
{
    nsILocalFile** orderedFileArray;
    PRUint32 countOfFilesInFileList;
    PRUint32 i;

    NS_ASSERTION(aFileList, "loser!");
    NS_ASSERTION(aWorkingSet, "loser!");
    NS_ASSERTION(aWorkingSet->IsValid(), "loser!");

    if(NS_FAILED(aFileList->Count(&countOfFilesInFileList)))
        return PR_FALSE;
    NS_ASSERTION(countOfFilesInFileList, "loser!");
    NS_ASSERTION(countOfFilesInFileList > aWorkingSet->GetFileCount(), "loser!");

    orderedFileArray = BuildOrderedFileArray(aSearchPath, aFileList, aWorkingSet);

    if(!orderedFileArray)
        return PR_FALSE;

    // Make enough space in aWorkingset for additions to xptiFile array.

    if(!aWorkingSet->ExtendFileArray(countOfFilesInFileList))   
        return PR_FALSE;

    // For each file that is not already in our working set, add any valid 
    // interfaces that don't conflict with previous interfaces added.
    for(i = 0; i < countOfFilesInFileList; i++)
    {
        nsILocalFile* file = orderedFileArray[i];

        nsCAutoString name;
        PRInt64 size;
        PRInt64 date;
        PRUint32 dir;
        if(NS_FAILED(file->GetFileSize(&size)) ||
           NS_FAILED(file->GetLastModifiedTime(&date)) ||
           NS_FAILED(file->GetNativeLeafName(name)) ||
           !aWorkingSet->FindDirectoryOfFile(file, &dir))
        {
            return PR_FALSE;
        }    
    

        if(xptiWorkingSet::NOT_FOUND != aWorkingSet->FindFile(dir, name.get()))
        {
            // This file was found in the working set, so skip it.       
            continue;
        }

        LOG_AUTOREG(("  finding interfaces in new file: %s\n", name.get()));

        xptiFile fileRecord;
        fileRecord = xptiFile(nsInt64(size), nsInt64(date), dir,
                              name.get(), aWorkingSet);

        if(xptiFileType::IsXPT(fileRecord.GetName()))
        {
            XPTHeader* header = ReadXPTFile(file, aWorkingSet);
            if(!header)
            {
                // XXX do something!
                NS_ERROR("");    
                continue;
            }

    
            xptiTypelib typelibRecord;
            typelibRecord.Init(aWorkingSet->GetFileCount());
    
            PRBool AddedFile = PR_FALSE;

            if(header->major_version >= XPT_MAJOR_INCOMPATIBLE_VERSION)
            {
                NS_ASSERTION(!header->num_interfaces,"bad libxpt");
                LOG_AUTOREG(("      file is version %d.%d  Type file of version %d.0 or higher can not be read.\n", (int)header->major_version, (int)header->minor_version, (int)XPT_MAJOR_INCOMPATIBLE_VERSION));
            }

            for(PRUint16 k = 0; k < header->num_interfaces; k++)
            {
                xptiInterfaceEntry* entry = nsnull;
    
                if(!VerifyAndAddEntryIfNew(aWorkingSet,
                                           header->interface_directory + k,
                                           typelibRecord,
                                           &entry))
                    return PR_FALSE;    
    
                if(!entry)
                    continue;
                
                // If this is the first interface we found for this file then
                // setup the fileRecord for the header and infos.
                if(!AddedFile)
                {
                    if(!fileRecord.SetHeader(header, aWorkingSet))
                    {
                        // XXX that would be bad.
                        return PR_FALSE;    
                    }
                    AddedFile = PR_TRUE;
                }
                fileRecord.GetGuts()->SetEntryAt(k, entry);
            }
            
            // This will correspond to typelibRecord above.
            aWorkingSet->AppendFile(fileRecord);
        }
        else // its another kind of archive
        {
            nsCOMPtr<nsIXPTLoader> loader =
                do_GetService(NS_ZIPLOADER_CONTRACTID);
            
            if (loader) {
                nsresult rv;
                
                nsCOMPtr<nsIXPTLoaderSink> sink =
                    new xptiZipLoaderSink(this, aWorkingSet);
                if (!sink)
                    return PR_FALSE;
                
                rv = loader->EnumerateEntries(file, sink);
                if (NS_FAILED(rv))
                    return PR_FALSE;
                // This will correspond to typelibRecord used in
                // xptiInterfaceInfoManager::FoundEntry.
                aWorkingSet->AppendFile(fileRecord);
            } else {
                NS_WARNING("Could not load XPT Zip loader");
            }
        }
    }

    return PR_TRUE;
}        

PRBool 
xptiInterfaceInfoManager::DoFullValidationMergeFromFileList(nsISupportsArray* aSearchPath,
                                                            nsISupportsArray* aFileList,
                                                            xptiWorkingSet* aWorkingSet)
{
    nsILocalFile** orderedFileArray;
    PRUint32 countOfFilesInFileList;
    PRUint32 i;

    NS_ASSERTION(aFileList, "loser!");

    if(!aWorkingSet->IsValid())
        return PR_FALSE;

    if(NS_FAILED(aFileList->Count(&countOfFilesInFileList)))
        return PR_FALSE;

    if(!countOfFilesInFileList)
    {
        // maybe there are no xpt files to register.  
        // a minimal install would have this case.
        return PR_TRUE;
    }

    orderedFileArray = BuildOrderedFileArray(aSearchPath, aFileList, aWorkingSet);

    if(!orderedFileArray)
        return PR_FALSE;

    // DEBUG_DumpFileArray(orderedFileArray, countOfFilesInFileList);

    // Make space in aWorkingset for a new xptiFile array.

    if(!aWorkingSet->NewFileArray(countOfFilesInFileList))   
        return PR_FALSE;

    aWorkingSet->ClearZipItems();
    aWorkingSet->ClearHashTables();

    // For each file, add any valid interfaces that don't conflict with 
    // previous interfaces added.
    for(i = 0; i < countOfFilesInFileList; i++)
    {
        nsILocalFile* file = orderedFileArray[i];

        nsCAutoString name;
        PRInt64 size;
        PRInt64 date;
        PRUint32 dir;
        if(NS_FAILED(file->GetFileSize(&size)) ||
           NS_FAILED(file->GetLastModifiedTime(&date)) ||
           NS_FAILED(file->GetNativeLeafName(name)) ||
           !aWorkingSet->FindDirectoryOfFile(file, &dir))
        {
            return PR_FALSE;
        }    

        LOG_AUTOREG(("  finding interfaces in file: %s\n", name.get()));
    
        xptiFile fileRecord;
        fileRecord = xptiFile(nsInt64(size), nsInt64(date), dir,
                              name.get(), aWorkingSet);

//        printf("* found %s\n", fileRecord.GetName());


        if(xptiFileType::IsXPT(fileRecord.GetName()))
        {
            XPTHeader* header = ReadXPTFile(file, aWorkingSet);
            if(!header)
            {
                // XXX do something!
                NS_ERROR("Unable to read an XPT file, turn logging on to see which file");    
                LOG_AUTOREG(("      unable to read file\n"));
                continue;
            }
    
            xptiTypelib typelibRecord;
            typelibRecord.Init(aWorkingSet->GetFileCount());
    
            PRBool AddedFile = PR_FALSE;

            if(header->major_version >= XPT_MAJOR_INCOMPATIBLE_VERSION)
            {
                NS_ASSERTION(!header->num_interfaces,"bad libxpt");
                LOG_AUTOREG(("      file is version %d.%d  Type file of version %d.0 or higher can not be read.\n", (int)header->major_version, (int)header->minor_version, (int)XPT_MAJOR_INCOMPATIBLE_VERSION));
            }

            for(PRUint16 k = 0; k < header->num_interfaces; k++)
            {
                xptiInterfaceEntry* entry = nsnull;
    
                if(!VerifyAndAddEntryIfNew(aWorkingSet,
                                           header->interface_directory + k,
                                           typelibRecord,
                                           &entry))
                    return PR_FALSE;    
    
                if(!entry)
                    continue;
                
                // If this is the first interface we found for this file then
                // setup the fileRecord for the header and infos.
                if(!AddedFile)
                {
                    if(!fileRecord.SetHeader(header, aWorkingSet))
                    {
                        // XXX that would be bad.
                        return PR_FALSE;    
                    }
                    AddedFile = PR_TRUE;
                }
                fileRecord.GetGuts()->SetEntryAt(k, entry);
            }
            
            // This will correspond to typelibRecord above.
            aWorkingSet->AppendFile(fileRecord);
        }

        else
        {
            nsCOMPtr<nsIXPTLoader> loader =
                do_GetService(NS_ZIPLOADER_CONTRACTID);
            
            if (loader) {
                nsresult rv;
                
                nsCOMPtr<nsIXPTLoaderSink> sink =
                    new xptiZipLoaderSink(this, aWorkingSet);
                if (!sink)
                    return PR_FALSE;
                
                rv = loader->EnumerateEntries(file, sink);
                if (NS_FAILED(rv))
                    return PR_FALSE;
                // This will correspond to typelibRecord used in
                // xptiInterfaceInfoManager::FoundEntry.
                aWorkingSet->AppendFile(fileRecord);
            } else {
                NS_WARNING("Could not load XPT Zip loader");
            }
        }
    }
    return PR_TRUE;
}        

NS_IMPL_ISUPPORTS1(xptiZipLoaderSink, nsIXPTLoaderSink)

// implement nsIXPTLoader
NS_IMETHODIMP
xptiZipLoaderSink::FoundEntry(const char* entryName,
                              PRInt32 index,
                              nsIInputStream *aStream)
{
    XPTHeader *header =
        xptiZipLoader::ReadXPTFileFromInputStream(aStream, mWorkingSet);
    if (!header)
        return NS_ERROR_OUT_OF_MEMORY;
    
    if (!mManager->FoundZipEntry(entryName, index, header, mWorkingSet))
        return NS_ERROR_FAILURE;
    
    return NS_OK;
}

// implement xptiEntrySink
PRBool 
xptiInterfaceInfoManager::FoundZipEntry(const char* entryName,
                                        int index,
                                        XPTHeader* header,
                                        xptiWorkingSet* aWorkingSet)
{

    NS_ASSERTION(entryName, "loser!");
    NS_ASSERTION(header, "loser!");
    NS_ASSERTION(aWorkingSet, "loser!");

    int countOfInterfacesAddedForItem = 0;
    xptiZipItem zipItemRecord(entryName, aWorkingSet);
    
    LOG_AUTOREG(("    finding interfaces in file: %s\n", entryName));

    if(header->major_version >= XPT_MAJOR_INCOMPATIBLE_VERSION)
    {
        NS_ASSERTION(!header->num_interfaces,"bad libxpt");
        LOG_AUTOREG(("      file is version %d.%d. Type file of version %d.0 or higher can not be read.\n", (int)header->major_version, (int)header->minor_version, (int)XPT_MAJOR_INCOMPATIBLE_VERSION));
    }

    if(!header->num_interfaces)
    {
        // We are not interested in files without interfaces.
        return PR_TRUE;
    }
    
    xptiTypelib typelibRecord;
    typelibRecord.Init(aWorkingSet->GetFileCount(),
                       aWorkingSet->GetZipItemCount());

    for(PRUint16 k = 0; k < header->num_interfaces; k++)
    {
        xptiInterfaceEntry* entry = nsnull;
    
        if(!VerifyAndAddEntryIfNew(aWorkingSet,
                                   header->interface_directory + k,
                                   typelibRecord,
                                   &entry))
            return PR_FALSE;    
    
        if(!entry)
            continue;

        // If this is the first interface we found for this item
        // then setup the zipItemRecord for the header and infos.
        if(!countOfInterfacesAddedForItem)
        {
            // XXX fix this!
            if(!zipItemRecord.SetHeader(header, aWorkingSet))
            {
                // XXX that would be bad.
                return PR_FALSE;    
            }
        }

        // zipItemRecord.GetGuts()->SetEntryAt(k, entry);
        ++countOfInterfacesAddedForItem;
    }   

    if(countOfInterfacesAddedForItem)
    {
        if(!aWorkingSet->GetZipItemFreeSpace())
        {
            if(!aWorkingSet->ExtendZipItemArray(
                aWorkingSet->GetZipItemCount() + 20))
            {        
                // out of space!
                return PR_FALSE;    
            }
        }
        aWorkingSet->AppendZipItem(zipItemRecord);
    } 
    return PR_TRUE;
}

PRBool 
xptiInterfaceInfoManager::VerifyAndAddEntryIfNew(xptiWorkingSet* aWorkingSet,
                                                 XPTInterfaceDirectoryEntry* iface,
                                                 const xptiTypelib& typelibRecord,
                                                 xptiInterfaceEntry** entryAdded)
{
    NS_ASSERTION(iface, "loser!");
    NS_ASSERTION(entryAdded, "loser!");

    *entryAdded = nsnull;

    if(!iface->interface_descriptor)
    {
        // Not resolved, ignore this one.
        // XXX full logging might note this...
        return PR_TRUE;
    }
    
    xptiHashEntry* hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(aWorkingSet->mIIDTable, &iface->iid, PL_DHASH_LOOKUP);

    xptiInterfaceEntry* entry = 
        PL_DHASH_ENTRY_IS_FREE(hashEntry) ? nsnull : hashEntry->value;
    
    if(entry)
    {
        // XXX validate this info to find possible inconsistencies
        LOG_AUTOREG(("      ignoring repeated interface: %s\n", iface->name));
        return PR_TRUE;
    }
    
    // Build a new xptiInterfaceEntry object and hook it up. 

    entry = xptiInterfaceEntry::NewEntry(iface->name, strlen(iface->name),
                                         iface->iid,
                                         typelibRecord, aWorkingSet);
    if(!entry)
    {
        // XXX bad!
        return PR_FALSE;    
    }

    //XXX  We should SetHeader too as part of the validation, no?
    entry->SetScriptableFlag(XPT_ID_IS_SCRIPTABLE(iface->interface_descriptor->flags));

    // Add our entry to the iid hashtable.

    hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(aWorkingSet->mNameTable, 
                             entry->GetTheName(), PL_DHASH_ADD);
    if(hashEntry)
        hashEntry->value = entry;
    
    // Add our entry to the name hashtable.

    hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(aWorkingSet->mIIDTable, 
                             entry->GetTheIID(), PL_DHASH_ADD);
    if(hashEntry)
        hashEntry->value = entry;
    
    *entryAdded = entry;

    LOG_AUTOREG(("      added interface: %s\n", iface->name));

    return PR_TRUE;
}

// local struct used to pass two pointers as one pointer
struct TwoWorkingSets
{
    TwoWorkingSets(xptiWorkingSet* src, xptiWorkingSet* dest)
        : aSrcWorkingSet(src), aDestWorkingSet(dest) {}

    xptiWorkingSet* aSrcWorkingSet;
    xptiWorkingSet* aDestWorkingSet;
};        

static PLDHashOperator
xpti_Merger(PLDHashTable *table, PLDHashEntryHdr *hdr,
            PRUint32 number, void *arg)
{
    xptiInterfaceEntry* srcEntry = ((xptiHashEntry*)hdr)->value;
    xptiWorkingSet* aSrcWorkingSet = ((TwoWorkingSets*)arg)->aSrcWorkingSet;
    xptiWorkingSet* aDestWorkingSet = ((TwoWorkingSets*)arg)->aDestWorkingSet;

    xptiHashEntry* hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(aDestWorkingSet->mIIDTable, 
                             srcEntry->GetTheIID(), PL_DHASH_LOOKUP);

    xptiInterfaceEntry* destEntry = 
        PL_DHASH_ENTRY_IS_FREE(hashEntry) ? nsnull : hashEntry->value;
    
    if(destEntry)
    {
        // Let's see if this is referring to the same exact typelib
         
        const char* destFilename = 
            aDestWorkingSet->GetTypelibFileName(destEntry->GetTypelibRecord());
        
        const char* srcFilename = 
            aSrcWorkingSet->GetTypelibFileName(srcEntry->GetTypelibRecord());
    
        if(0 == PL_strcmp(destFilename, srcFilename) && 
           (destEntry->GetTypelibRecord().GetZipItemIndex() ==
            srcEntry->GetTypelibRecord().GetZipItemIndex()))
        {
            // This is the same item.
            // But... Let's make sure they didn't change the interface name.
            // There are wacky developers that do stuff like that!
            if(0 == PL_strcmp(destEntry->GetTheName(), srcEntry->GetTheName()))
                return PL_DHASH_NEXT;
        }
    }
    
    // Clone the xptiInterfaceEntry into our destination WorkingSet.

    xptiTypelib typelibRecord;

    uint16 fileIndex = srcEntry->GetTypelibRecord().GetFileIndex();
    uint16 zipItemIndex = srcEntry->GetTypelibRecord().GetZipItemIndex();
    
    fileIndex += aDestWorkingSet->mFileMergeOffsetMap[fileIndex];
    
    // If it is not a zipItem, then the original index is fine.
    if(srcEntry->GetTypelibRecord().IsZip())
        zipItemIndex += aDestWorkingSet->mZipItemMergeOffsetMap[zipItemIndex];

    typelibRecord.Init(fileIndex, zipItemIndex);
                
    destEntry = xptiInterfaceEntry::NewEntry(*srcEntry, typelibRecord, 
                                             aDestWorkingSet);
    if(!destEntry)
    {
        // XXX bad! should log
        return PL_DHASH_NEXT;
    }


    // Add our entry to the iid hashtable.

    hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(aDestWorkingSet->mNameTable, 
                             destEntry->GetTheName(), PL_DHASH_ADD);
    if(hashEntry)
        hashEntry->value = destEntry;
    
    // Add our entry to the name hashtable.

    hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(aDestWorkingSet->mIIDTable, 
                             destEntry->GetTheIID(), PL_DHASH_ADD);
    if(hashEntry)
        hashEntry->value = destEntry;

    return PL_DHASH_NEXT;
}       

PRBool 
xptiInterfaceInfoManager::MergeWorkingSets(xptiWorkingSet* aDestWorkingSet,
                                           xptiWorkingSet* aSrcWorkingSet)
{

    PRUint32 i;

    // Combine file lists.

    PRUint32 originalFileCount = aDestWorkingSet->GetFileCount();
    PRUint32 additionalFileCount = aSrcWorkingSet->GetFileCount();

    // Create a new array big enough to hold both lists and copy existing files

    if(additionalFileCount)
    {
        if(!aDestWorkingSet->ExtendFileArray(originalFileCount +
                                             additionalFileCount))
            return PR_FALSE;
    
        // Now we are where we started, but we know we have enough space.
    
        // Prepare offset array for later fixups. 
        // NOTE: Storing with dest, but alloc'ing from src. This is intentional.
        aDestWorkingSet->mFileMergeOffsetMap = (PRUint32*)
            XPT_CALLOC(aSrcWorkingSet->GetStructArena(),
                       additionalFileCount * sizeof(PRUint32)); 
        if(!aDestWorkingSet->mFileMergeOffsetMap)
            return PR_FALSE;
    }

    for(i = 0; i < additionalFileCount; ++i)
    {
        xptiFile& srcFile = aSrcWorkingSet->GetFileAt(i);
        PRUint32 k;
        for(k = 0; k < originalFileCount; ++k)
        {
            // If file (with same name, date, and time) is in both lists
            // then reuse that record.
            xptiFile& destFile = aDestWorkingSet->GetFileAt(k);
            if(srcFile.Equals(destFile))
            {
                aDestWorkingSet->mFileMergeOffsetMap[i] = k - i;
                break;    
            }
        }
        if(k == originalFileCount)
        {
            // No match found, tack it on the end.

            PRUint32 newIndex = aDestWorkingSet->GetFileCount();

            aDestWorkingSet->AppendFile(xptiFile(srcFile, aDestWorkingSet));

            // Fixup the merge offset map.
            aDestWorkingSet->mFileMergeOffsetMap[i] = newIndex - i;
        }
    }

    // Combine ZipItem lists.

    PRUint32 originalZipItemCount = aDestWorkingSet->GetZipItemCount();
    PRUint32 additionalZipItemCount = aSrcWorkingSet->GetZipItemCount();

    // Create a new array big enough to hold both lists and copy existing ZipItems

    if(additionalZipItemCount)
    {
        if(!aDestWorkingSet->ExtendZipItemArray(originalZipItemCount +
                                                additionalZipItemCount))
            return PR_FALSE;
    
        // Now we are where we started, but we know we have enough space.
    
        // Prepare offset array for later fixups. 
        // NOTE: Storing with dest, but alloc'ing from src. This is intentional.
        aDestWorkingSet->mZipItemMergeOffsetMap = (PRUint32*)
            XPT_CALLOC(aSrcWorkingSet->GetStructArena(),
                       additionalZipItemCount * sizeof(PRUint32)); 
        if(!aDestWorkingSet->mZipItemMergeOffsetMap)
            return PR_FALSE;
    }

    for(i = 0; i < additionalZipItemCount; ++i)
    {
        xptiZipItem& srcZipItem = aSrcWorkingSet->GetZipItemAt(i);
        PRUint32 k;
        for(k = 0; k < originalZipItemCount; ++k)
        {
            // If ZipItem (with same name) is in both lists
            // then reuse that record.
            xptiZipItem& destZipItem = aDestWorkingSet->GetZipItemAt(k);
            if(srcZipItem.Equals(destZipItem))
            {
                aDestWorkingSet->mZipItemMergeOffsetMap[i] = k - i;
                break;    
            }
        }
        if(k == originalZipItemCount)
        {
            // No match found, tack it on the end.

            PRUint32 newIndex = aDestWorkingSet->GetZipItemCount();

            aDestWorkingSet->AppendZipItem(
                    xptiZipItem(srcZipItem, aDestWorkingSet));

            // Fixup the merge offset map.
            aDestWorkingSet->mZipItemMergeOffsetMap[i] = newIndex - i;
        }
    }

    // Migrate xptiInterfaceInfos

    TwoWorkingSets sets(aSrcWorkingSet, aDestWorkingSet);

    PL_DHashTableEnumerate(aSrcWorkingSet->mNameTable, xpti_Merger, &sets);

    return PR_TRUE;
}        

PRBool 
xptiInterfaceInfoManager::DEBUG_DumpFileList(nsISupportsArray* aFileList)
{
    PRUint32 count;

    if(NS_FAILED(aFileList->Count(&count)))
        return PR_FALSE;
    
    for(PRUint32 i = 0; i < count; i++)
    {
        nsCOMPtr<nsIFile> file;
        aFileList->QueryElementAt(i, NS_GET_IID(nsILocalFile), getter_AddRefs(file));
        if(!file)
            return PR_FALSE;

        nsCAutoString name;
        if(NS_FAILED(file->GetNativeLeafName(name)))
            return PR_FALSE;

        printf("* found %s\n", name.get());
    }
    return PR_TRUE;
}

PRBool 
xptiInterfaceInfoManager::DEBUG_DumpFileListInWorkingSet(xptiWorkingSet* aWorkingSet)
{
    for(PRUint16 i = 0; i < aWorkingSet->GetFileCount(); ++i)
    {
        xptiFile& record = aWorkingSet->GetFileAt(i);
    
        printf("! has %s\n", record.GetName());
    }        
    return PR_TRUE;
}        

PRBool 
xptiInterfaceInfoManager::DEBUG_DumpFileArray(nsILocalFile** aFileArray, 
                                              PRUint32 count)
{
    // dump the sorted list
    for(PRUint32 i = 0; i < count; ++i)
    {
        nsILocalFile* file = aFileArray[i];
    
        nsCAutoString name;
        if(NS_FAILED(file->GetNativeLeafName(name)))
            return PR_FALSE;

        printf("found file: %s\n", name.get());
    }        
    return PR_TRUE;        
}        

/***************************************************************************/

// static 
void 
xptiInterfaceInfoManager::WriteToLog(const char *fmt, ...)
{
    if(!gInterfaceInfoManager)
        return;

    PRFileDesc* fd = gInterfaceInfoManager->GetOpenLogFile();
    if(fd)
    {
        va_list ap;
        va_start(ap, fmt);
        PR_vfprintf(fd, fmt, ap);
        va_end(ap);
    }
}        

static PLDHashOperator
xpti_ResolvedFileNameLogger(PLDHashTable *table, PLDHashEntryHdr *hdr,
                            PRUint32 number, void *arg)
{
    xptiInterfaceEntry* entry = ((xptiHashEntry*)hdr)->value;
    xptiInterfaceInfoManager* mgr = (xptiInterfaceInfoManager*) arg;

    if(entry->IsFullyResolved())
    {
        xptiWorkingSet*  aWorkingSet = mgr->GetWorkingSet();
        PRFileDesc* fd = mgr->GetOpenLogFile();

        const xptiTypelib& typelib = entry->GetTypelibRecord();
        const char* filename = 
                aWorkingSet->GetFileAt(typelib.GetFileIndex()).GetName();
        
        if(typelib.IsZip())
        {
            const char* zipItemName = 
                aWorkingSet->GetZipItemAt(typelib.GetZipItemIndex()).GetName();
            PR_fprintf(fd, "xpti used interface: %s from %s::%s\n", 
                       entry->GetTheName(), filename, zipItemName);
        }    
        else
        {
            PR_fprintf(fd, "xpti used interface: %s from %s\n", 
                       entry->GetTheName(), filename);
        }
    }
    return PL_DHASH_NEXT;
}

void   
xptiInterfaceInfoManager::LogStats()
{
    PRUint32 i;
    
    // This sets what will be returned by GetOpenLogFile().
    xptiAutoLog autoLog(this, mStatsLogFile, PR_FALSE);

    PRFileDesc* fd = GetOpenLogFile();
    if(!fd)
        return;

    // Show names of xpt (only) files from which at least one interface 
    // was resolved.

    PRUint32 fileCount = mWorkingSet.GetFileCount();
    for(i = 0; i < fileCount; ++i)
    {
        xptiFile& f = mWorkingSet.GetFileAt(i);
        if(f.GetGuts())
            PR_fprintf(fd, "xpti used file: %s\n", f.GetName());
    }

    PR_fprintf(fd, "\n");

    // Show names of xptfiles loaded from zips from which at least 
    // one interface was resolved.

    PRUint32 zipItemCount = mWorkingSet.GetZipItemCount();
    for(i = 0; i < zipItemCount; ++i)
    {
        xptiZipItem& zi = mWorkingSet.GetZipItemAt(i);
        if(zi.GetGuts())                           
            PR_fprintf(fd, "xpti used file from zip: %s\n", zi.GetName());
    }

    PR_fprintf(fd, "\n");

    // Show name of each interface that was fully resolved and the name
    // of the file and (perhaps) zip from which it was loaded.

    PL_DHashTableEnumerate(mWorkingSet.mNameTable, 
                           xpti_ResolvedFileNameLogger, this);

} 

/***************************************************************************/

// this is a private helper
static nsresult 
EntryToInfo(xptiInterfaceEntry* entry, nsIInterfaceInfo **_retval)
{
    xptiInterfaceInfo* info;
    nsresult rv;

    if(!entry)
    {
        *_retval = nsnull;
        return NS_ERROR_FAILURE;    
    }

    rv = entry->GetInterfaceInfo(&info);
    if(NS_FAILED(rv))
        return rv;

    // Transfer the AddRef done by GetInterfaceInfo.
    *_retval = static_cast<nsIInterfaceInfo*>(info);
    return NS_OK;    
}

xptiInterfaceEntry*
xptiInterfaceInfoManager::GetInterfaceEntryForIID(const nsIID *iid)
{
    xptiHashEntry *hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(mWorkingSet.mIIDTable, iid, PL_DHASH_LOOKUP);
    return PL_DHASH_ENTRY_IS_FREE(hashEntry) ? nsnull : hashEntry->value;
}

/* nsIInterfaceInfo getInfoForIID (in nsIIDPtr iid); */
NS_IMETHODIMP xptiInterfaceInfoManager::GetInfoForIID(const nsIID * iid, nsIInterfaceInfo **_retval)
{
    NS_ASSERTION(iid, "bad param");
    NS_ASSERTION(_retval, "bad param");

    xptiInterfaceEntry* entry = GetInterfaceEntryForIID(iid);
    return EntryToInfo(entry, _retval);
}

/* nsIInterfaceInfo getInfoForName (in string name); */
NS_IMETHODIMP xptiInterfaceInfoManager::GetInfoForName(const char *name, nsIInterfaceInfo **_retval)
{
    NS_ASSERTION(name, "bad param");
    NS_ASSERTION(_retval, "bad param");

    xptiHashEntry* hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(mWorkingSet.mNameTable, name, PL_DHASH_LOOKUP);

    xptiInterfaceEntry* entry = 
        PL_DHASH_ENTRY_IS_FREE(hashEntry) ? nsnull : hashEntry->value;

    return EntryToInfo(entry, _retval);
}

/* nsIIDPtr getIIDForName (in string name); */
NS_IMETHODIMP xptiInterfaceInfoManager::GetIIDForName(const char *name, nsIID * *_retval)
{
    NS_ASSERTION(name, "bad param");
    NS_ASSERTION(_retval, "bad param");

    xptiHashEntry* hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(mWorkingSet.mNameTable, name, PL_DHASH_LOOKUP);

    xptiInterfaceEntry* entry = 
        PL_DHASH_ENTRY_IS_FREE(hashEntry) ? nsnull : hashEntry->value;

    if(!entry)
    {
        *_retval = nsnull;
        return NS_ERROR_FAILURE;    
    }

    return entry->GetIID(_retval);
}

/* string getNameForIID (in nsIIDPtr iid); */
NS_IMETHODIMP xptiInterfaceInfoManager::GetNameForIID(const nsIID * iid, char **_retval)
{
    NS_ASSERTION(iid, "bad param");
    NS_ASSERTION(_retval, "bad param");

    xptiHashEntry* hashEntry = (xptiHashEntry*)
        PL_DHashTableOperate(mWorkingSet.mIIDTable, iid, PL_DHASH_LOOKUP);

    xptiInterfaceEntry* entry = 
        PL_DHASH_ENTRY_IS_FREE(hashEntry) ? nsnull : hashEntry->value;

    if(!entry)
    {
        *_retval = nsnull;
        return NS_ERROR_FAILURE;    
    }

    return entry->GetName(_retval);
}

static PLDHashOperator
xpti_ArrayAppender(PLDHashTable *table, PLDHashEntryHdr *hdr,
                   PRUint32 number, void *arg)
{
    xptiInterfaceEntry* entry = ((xptiHashEntry*)hdr)->value;
    nsISupportsArray* array = (nsISupportsArray*) arg;

    nsCOMPtr<nsIInterfaceInfo> ii;
    if(NS_SUCCEEDED(EntryToInfo(entry, getter_AddRefs(ii))))
        array->AppendElement(ii);
    return PL_DHASH_NEXT;
}

/* nsIEnumerator enumerateInterfaces (); */
NS_IMETHODIMP xptiInterfaceInfoManager::EnumerateInterfaces(nsIEnumerator **_retval)
{
    // I didn't want to incur the size overhead of using nsHashtable just to
    // make building an enumerator easier. So, this code makes a snapshot of 
    // the table using an nsISupportsArray and builds an enumerator for that.
    // We can afford this transient cost.

    nsCOMPtr<nsISupportsArray> array;
    NS_NewISupportsArray(getter_AddRefs(array));
    if(!array)
        return NS_ERROR_UNEXPECTED;

    PL_DHashTableEnumerate(mWorkingSet.mNameTable, xpti_ArrayAppender, array);
    
    return array->Enumerate(_retval);
}

struct ArrayAndPrefix
{
    nsISupportsArray* array;
    const char*       prefix;
    PRUint32          length;
};

static PLDHashOperator
xpti_ArrayPrefixAppender(PLDHashTable *table, PLDHashEntryHdr *hdr,
                         PRUint32 number, void *arg)
{
    xptiInterfaceEntry* entry = ((xptiHashEntry*)hdr)->value;
    ArrayAndPrefix* args = (ArrayAndPrefix*) arg;

    const char* name = entry->GetTheName();
    if(name != PL_strnstr(name, args->prefix, args->length))
        return PL_DHASH_NEXT;

    nsCOMPtr<nsIInterfaceInfo> ii;
    if(NS_SUCCEEDED(EntryToInfo(entry, getter_AddRefs(ii))))
        args->array->AppendElement(ii);
    return PL_DHASH_NEXT;
}

/* nsIEnumerator enumerateInterfacesWhoseNamesStartWith (in string prefix); */
NS_IMETHODIMP xptiInterfaceInfoManager::EnumerateInterfacesWhoseNamesStartWith(const char *prefix, nsIEnumerator **_retval)
{
    nsCOMPtr<nsISupportsArray> array;
    NS_NewISupportsArray(getter_AddRefs(array));
    if(!array)
        return NS_ERROR_UNEXPECTED;

    ArrayAndPrefix args = {array, prefix, PL_strlen(prefix)};
    PL_DHashTableEnumerate(mWorkingSet.mNameTable, xpti_ArrayPrefixAppender, &args);
    
    return array->Enumerate(_retval);
}

/* void autoRegisterInterfaces (); */
NS_IMETHODIMP xptiInterfaceInfoManager::AutoRegisterInterfaces()
{
    nsCOMPtr<nsISupportsArray> fileList;
    AutoRegMode mode;
    PRBool ok;

    nsAutoLock lock(xptiInterfaceInfoManager::GetAutoRegLock(this));

    xptiWorkingSet workingSet(mSearchPath);
    if(!workingSet.IsValid())
        return NS_ERROR_UNEXPECTED;

    // This sets what will be returned by GetOpenLogFile().
    xptiAutoLog autoLog(this, mAutoRegLogFile, PR_TRUE);

    LOG_AUTOREG(("start AutoRegister\n"));

    // We re-read the manifest rather than muck with the 'live' one.
    // It is OK if this fails.
    // XXX But we should track failure as a warning.
    ok = xptiManifest::Read(this, &workingSet);

    LOG_AUTOREG(("read of manifest %s\n", ok ? "successful" : "FAILED"));

    // Grovel for all the typelibs we can find (in .xpt or .zip, .jar,...).
    if(!BuildFileList(mSearchPath, getter_AddRefs(fileList)) || !fileList)
        return NS_ERROR_UNEXPECTED;
    
    // DEBUG_DumpFileList(fileList);

    // Check to see how much work we need to do.
    mode = DetermineAutoRegStrategy(mSearchPath, fileList, &workingSet);

    switch(mode)
    {
    case NO_FILES_CHANGED:
        LOG_AUTOREG(("autoreg strategy: no files changed\n"));
        LOG_AUTOREG(("successful end of AutoRegister\n"));
        return NS_OK;
    case FILES_ADDED_ONLY:
        LOG_AUTOREG(("autoreg strategy: files added only\n"));
        if(!AddOnlyNewFilesFromFileList(mSearchPath, fileList, &workingSet))
        {
            LOG_AUTOREG(("FAILED to add new files\n"));
            return NS_ERROR_UNEXPECTED;
        }
        break;
    case FULL_VALIDATION_REQUIRED:
        LOG_AUTOREG(("autoreg strategy: doing full validation merge\n"));
        if(!DoFullValidationMergeFromFileList(mSearchPath, fileList, &workingSet))
        {
            LOG_AUTOREG(("FAILED to do full validation\n"));
            return NS_ERROR_UNEXPECTED;
        }
        break;
    default:
        NS_ERROR("switch missing a case");
        return NS_ERROR_UNEXPECTED;
    }

    // Failure to write the manifest is not fatal in production builds.
    // However, this would require the next startup to find and read all the
    // xpt files. This will make that startup slower. If this ever becomes a 
    // chronic problem for anyone, then we'll want to figure out why!
    
    if(!xptiManifest::Write(this, &workingSet))
    {
        LOG_AUTOREG(("FAILED to write manifest\n"));
        NS_ERROR("Failed to write xpti manifest!");
    }
    
    if(!MergeWorkingSets(&mWorkingSet, &workingSet))
    {
        LOG_AUTOREG(("FAILED to merge into live workingset\n"));
        return NS_ERROR_UNEXPECTED;
    }

//    DEBUG_DumpFileListInWorkingSet(mWorkingSet);

    LOG_AUTOREG(("successful end of AutoRegister\n"));

    return NS_OK;
}

/***************************************************************************/

/* void addAdditionalManager (in nsIInterfaceInfoManager manager); */
NS_IMETHODIMP xptiInterfaceInfoManager::AddAdditionalManager(nsIInterfaceInfoManager *manager)
{
    nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(manager);
    nsISupports* ptrToAdd = weakRef ? 
                    static_cast<nsISupports*>(weakRef) :
                    static_cast<nsISupports*>(manager);
    { // scoped lock...
        nsAutoLock lock(mAdditionalManagersLock);
        if(mAdditionalManagers.IndexOf(ptrToAdd) != -1)
            return NS_ERROR_FAILURE;
        if(!mAdditionalManagers.AppendObject(ptrToAdd))
            return NS_ERROR_OUT_OF_MEMORY;
    }
    return NS_OK;
}

/* void removeAdditionalManager (in nsIInterfaceInfoManager manager); */
NS_IMETHODIMP xptiInterfaceInfoManager::RemoveAdditionalManager(nsIInterfaceInfoManager *manager)
{
    nsCOMPtr<nsIWeakReference> weakRef = do_GetWeakReference(manager);
    nsISupports* ptrToRemove = weakRef ? 
                    static_cast<nsISupports*>(weakRef) :
                    static_cast<nsISupports*>(manager);
    { // scoped lock...
        nsAutoLock lock(mAdditionalManagersLock);
        if(!mAdditionalManagers.RemoveObject(ptrToRemove))
            return NS_ERROR_FAILURE;
    }
    return NS_OK;
}

/* PRBool hasAdditionalManagers (); */
NS_IMETHODIMP xptiInterfaceInfoManager::HasAdditionalManagers(PRBool *_retval)
{
    *_retval = mAdditionalManagers.Count() > 0;
    return NS_OK;
}

/* nsISimpleEnumerator enumerateAdditionalManagers (); */
NS_IMETHODIMP xptiInterfaceInfoManager::EnumerateAdditionalManagers(nsISimpleEnumerator **_retval)
{
    nsAutoLock lock(mAdditionalManagersLock);

    nsCOMArray<nsISupports> managerArray(mAdditionalManagers);
    /* Resolve all the weak references in the array. */
    for(PRInt32 i = managerArray.Count(); i--; )
    {
        nsISupports *raw = managerArray.ObjectAt(i);
        if(!raw)
            return NS_ERROR_FAILURE;
        nsCOMPtr<nsIWeakReference> weakRef = do_QueryInterface(raw);
        if(weakRef)
        {
            nsCOMPtr<nsIInterfaceInfoManager> manager = 
                do_QueryReferent(weakRef);
            if(manager)
            {
                if(!managerArray.ReplaceObjectAt(manager, i))
                    return NS_ERROR_FAILURE;
            }
            else
            {
                // The manager is no more. Remove the element.
                mAdditionalManagers.RemoveObjectAt(i);
                managerArray.RemoveObjectAt(i);
            }
        }
    }
    
    return NS_NewArrayEnumerator(_retval, managerArray);
}
