text:
#include "OgreStableHeaders.h"
#include "OgreScriptLoader.h"

namespace Ogre {

    //-----------------------------------------------------------------------
    template<> ResourceGroupManager* Singleton<ResourceGroupManager>::msSingleton = 0;
    ResourceGroupManager* ResourceGroupManager::getSingletonPtr(void)
    {
        return msSingleton;
    }
    ResourceGroupManager& ResourceGroupManager::getSingleton(void)
    {
        assert( msSingleton );  return ( *msSingleton );
    }

    const char* const RGN_DEFAULT = "General";
    const char* const RGN_INTERNAL = "OgreInternal";
    const char* const RGN_AUTODETECT = "OgreAutodetect";

    const String ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME = RGN_DEFAULT;
    const String ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME = RGN_INTERNAL;
    const String ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME = RGN_AUTODETECT;

    // A reference count of 3 means that only RGM and RM have references
    // RGM has one (this one) and RM has 2 (by name and by handle)
    const long ResourceGroupManager::RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS = 3;
    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    ResourceGroupManager::ResourceGroupManager()
        : mLoadingListener(0), mCurrentGroup(0)
    {
        // Create the 'General' group
        createResourceGroup(DEFAULT_RESOURCE_GROUP_NAME, true); // the "General" group is synonymous to global pool
        // Create the 'Internal' group
        createResourceGroup(INTERNAL_RESOURCE_GROUP_NAME, true);
        // Create the 'Autodetect' group (only used for temp storage)
        createResourceGroup(AUTODETECT_RESOURCE_GROUP_NAME, true); // autodetect includes the global pool
        // default world group to the default group
        mWorldGroupName = DEFAULT_RESOURCE_GROUP_NAME;
    }
    //-----------------------------------------------------------------------
    ResourceGroupManager::~ResourceGroupManager()
    {
        // delete all resource groups
        ResourceGroupMap::iterator i, iend;
        iend = mResourceGroupMap.end();
        for (auto& g : mResourceGroupMap)
        {
            deleteGroup(g.second);
        }
        mResourceGroupMap.clear();
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::createResourceGroup(const String& name, bool inGlobalPool)
    {
        LogManager::getSingleton().logMessage("Creating resource group " + name);
        if (getResourceGroup(name))
        {
            OGRE_EXCEPT(Exception::ERR_DUPLICATE_ITEM,
                "Resource group with name '" + name + "' already exists!",
                "ResourceGroupManager::createResourceGroup");
        }
        ResourceGroup* grp = OGRE_NEW_T(ResourceGroup, MEMCATEGORY_RESOURCE)();
        grp->groupStatus = ResourceGroup::UNINITIALSED;
        grp->name = name;
        grp->inGlobalPool = inGlobalPool;
        grp->customStageCount = 0;

        OGRE_LOCK_AUTO_MUTEX;
        mResourceGroupMap.emplace(name, grp);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::initialiseResourceGroup(const String& name)
    {
        LogManager::getSingleton().logMessage("Initialising resource group " + name);
        ResourceGroup* grp = getResourceGroup(name, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex;

        if (grp->groupStatus == ResourceGroup::UNINITIALSED)
        {
            // in the process of initialising
            grp->groupStatus = ResourceGroup::INITIALISING;
            // Set current group
            parseResourceGroupScripts(grp);
            mCurrentGroup = grp;
            LogManager::getSingleton().logMessage("Creating resources for group " + name);
            createDeclaredResources(grp);
            grp->groupStatus = ResourceGroup::INITIALISED;
            LogManager::getSingleton().logMessage("All done");
            // Reset current group
            mCurrentGroup = 0;
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::initialiseAllResourceGroups(void)
    {
            OGRE_LOCK_AUTO_MUTEX;

        // Intialise all declared resource groups
        for (auto& g : mResourceGroupMap)
        {
            ResourceGroup* grp = g.second;
            OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
            if (grp->groupStatus == ResourceGroup::UNINITIALSED)
            {
                // in the process of initialising
                grp->groupStatus = ResourceGroup::INITIALISING;
                // Set current group
                mCurrentGroup = grp;
                parseResourceGroupScripts(grp);
                LogManager::getSingleton().logMessage("Creating resources for group " + g.first);
                createDeclaredResources(grp);
                grp->groupStatus = ResourceGroup::INITIALISED;
                LogManager::getSingleton().logMessage("All done");
                // Reset current group
                mCurrentGroup = 0;
            }
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::prepareResourceGroup(const String& name)
    {
        LogManager::getSingleton().stream() << "Preparing resource group '" << name << "'";
        // load all created resources
        ResourceGroup* grp = getResourceGroup(name, true);
        OGRE_LOCK_AUTO_MUTEX;
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
        // Set current group
        mCurrentGroup = grp;

        // Count up resources for starting event
        size_t resourceCount = 0;
        for (auto& oi : grp->loadResourceOrderMap)
        {
            resourceCount += oi.second.size();
        }

        fireResourceGroupPrepareStarted(name, resourceCount);

        // Now load for real
        for (auto& oi : grp->loadResourceOrderMap)
        {
            size_t n = 0;
            LoadUnloadResourceList::iterator l = oi.second.begin();
            while (l != oi.second.end())
            {
                ResourcePtr res = *l;

                // Fire resource events no matter whether resource needs preparing
                // or not. This ensures that the number of callbacks
                // matches the number originally estimated, which is important
                // for progress bars.
                fireResourcePrepareStarted(res);

                // If preparing one of these resources cascade-prepares another resource,
                // the list will get longer! But these should be prepared immediately
                // Call prepare regardless, already prepared or loaded resources will be skipped
                res->prepare();

                fireResourcePrepareEnded();

                ++n;

                // Did the resource change group? if so, our iterator will have
                // been invalidated
                if (res->getGroup() != name)
                {
                    l = oi.second.begin();
                    std::advance(l, n);
                }
                else
                {
                    ++l;
                }
            }
        }
        fireResourceGroupPrepareEnded(name);

        // reset current group
        mCurrentGroup = 0;

        LogManager::getSingleton().logMessage("Finished preparing resource group " + name);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::loadResourceGroup(const String& name)
    {
        LogManager::getSingleton().stream() << "Loading resource group '" << name << "'";
        // load all created resources
        ResourceGroup* grp = getResourceGroup(name, true);
        OGRE_LOCK_AUTO_MUTEX;
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
        // Set current group
        mCurrentGroup = grp;

        // Count up resources for starting event
        size_t resourceCount = grp->customStageCount;
        for (auto& oi : grp->loadResourceOrderMap)
        {
            resourceCount += oi.second.size();
        }

        fireResourceGroupLoadStarted(name, resourceCount);

        // Now load for real
        for (auto& oi : grp->loadResourceOrderMap)
        {
            size_t n = 0;
            auto l = oi.second.begin();
            while (l != oi.second.end())
            {
                ResourcePtr res = *l;

                // Fire resource events no matter whether resource is already
                // loaded or not. This ensures that the number of callbacks
                // matches the number originally estimated, which is important
                // for progress bars.
                fireResourceLoadStarted(res);

                // If loading one of these resources cascade-loads another resource,
                // the list will get longer! But these should be loaded immediately
                // Call load regardless, already loaded resources will be skipped
                res->load();

                fireResourceLoadEnded();

                ++n;

                // Did the resource change group? if so, our iterator will have
                // been invalidated
                if (res->getGroup() != name)
                {
                    l = oi.second.begin();
                    std::advance(l, n);
                }
                else
                {
                    ++l;
                }
            }
        }
        fireResourceGroupLoadEnded(name);

        // group is loaded
        grp->groupStatus = ResourceGroup::LOADED;

        // reset current group
        mCurrentGroup = 0;

        LogManager::getSingleton().logMessage("Finished loading resource group " + name);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::unloadResourceGroup(const String& name, bool reloadableOnly)
    {
        LogManager::getSingleton().logMessage("Unloading resource group " + name);
        ResourceGroup* grp = getResourceGroup(name, true);
        OGRE_LOCK_AUTO_MUTEX;
        // Set current group
        mCurrentGroup = grp;
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // unload in reverse order
        for (auto& oi : grp->loadResourceOrderMap)
        {
            for (auto& l : oi.second)
            {
                Resource* resource = l.get();
                if (!reloadableOnly || resource->isReloadable())
                {
                    resource->unload();
                }
            }
        }

        grp->groupStatus = ResourceGroup::INITIALISED;

        // reset current group
        mCurrentGroup = 0;
        LogManager::getSingleton().logMessage("Finished unloading resource group " + name);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::unloadUnreferencedResourcesInGroup(
        const String& name, bool reloadableOnly )
    {
        LogManager::getSingleton().logMessage(
            "Unloading unused resources in resource group " + name);
        ResourceGroup* grp = getResourceGroup(name, true);
        OGRE_LOCK_AUTO_MUTEX;
        // Set current group
        mCurrentGroup = grp;

        ResourceGroup::LoadResourceOrderMap::reverse_iterator oi;
        // unload in reverse order
        for (oi = grp->loadResourceOrderMap.rbegin(); oi != grp->loadResourceOrderMap.rend(); ++oi)
        {
            for (auto& l : oi->second)
            {
                // A use count of 3 means that only RGM and RM have references
                // RGM has one (this one) and RM has 2 (by name and by handle)
                if (l.use_count() == RESOURCE_SYSTEM_NUM_REFERENCE_COUNTS)
                {
                    Resource* resource = l.get();
                    if (!reloadableOnly || resource->isReloadable())
                    {
                        resource->unload();
                    }
                }
            }
        }

        grp->groupStatus = ResourceGroup::INITIALISED;

        // reset current group
        mCurrentGroup = 0;
        LogManager::getSingleton().logMessage(
            "Finished unloading unused resources in resource group " + name);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::clearResourceGroup(const String& name)
    {
            LogManager::getSingleton().logMessage("Clearing resource group " + name);
        ResourceGroup* grp = getResourceGroup(name, true);
        OGRE_LOCK_AUTO_MUTEX;
        // set current group
        mCurrentGroup = grp;
        dropGroupContents(grp);
        // clear initialised flag
        grp->groupStatus = ResourceGroup::UNINITIALSED;
        // reset current group
        mCurrentGroup = 0;
        LogManager::getSingleton().logMessage("Finished clearing resource group " + name);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::destroyResourceGroup(const String& name)
    {
        LogManager::getSingleton().logMessage("Destroying resource group " + name);
        ResourceGroup* grp = getResourceGroup(name, true);
        OGRE_LOCK_AUTO_MUTEX;
        // set current group
        mCurrentGroup = grp;
        unloadResourceGroup(name, false); // will throw an exception if name not valid
        dropGroupContents(grp);
        deleteGroup(grp);
        mResourceGroupMap.erase(mResourceGroupMap.find(name));
        // reset current group
        mCurrentGroup = 0;
    }
    //-----------------------------------------------------------------------
    bool ResourceGroupManager::isResourceGroupInitialised(const String& name) const
    {
        ResourceGroup* grp = getResourceGroup(name, true);
        return (grp->groupStatus != ResourceGroup::UNINITIALSED &&
            grp->groupStatus != ResourceGroup::INITIALISING);
    }
    //-----------------------------------------------------------------------
    bool ResourceGroupManager::isResourceGroupLoaded(const String& name) const
    {
        return getResourceGroup(name, true)->groupStatus == ResourceGroup::LOADED;
    }
    //-----------------------------------------------------------------------
    bool ResourceGroupManager::resourceGroupExists(const String& name) const
    {
        return getResourceGroup(name) ? true : false;
    }
    //-----------------------------------------------------------------------
    bool ResourceGroupManager::resourceLocationExists(const String& name,
        const String& resGroup) const
    {
        ResourceGroup* grp = getResourceGroup(resGroup);
        if (!grp)
            return false;

        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        for (auto& li : grp->locationList)
        {
            Archive* pArch = li.archive;
            if (pArch->getName() == name)
                // Delete indexes
                return true;
        }
        return false;
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::addResourceLocation(const String& name,
        const String& locType, const String& resGroup, bool recursive, bool readOnly)
    {
        // Get archive
        Archive* pArch = ArchiveManager::getSingleton().load( name, locType, readOnly );
        // Add to location list

        ResourceLocation loc = {pArch, recursive};
        StringVectorPtr vec = pArch->find("*", recursive);

        ResourceGroup* grp = getResourceGroup(resGroup);
        if (!grp)
        {
            createResourceGroup(resGroup);
            grp = getResourceGroup(resGroup);
        }

        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
        grp->locationList.push_back(loc);

        // Index resources
        for (auto& s : *vec)
            grp->addToIndex(s, pArch);

        StringStream msg;
        msg << "Added resource location '" << name << "' of type '" << locType
            << "' to resource group '" << resGroup << "'";
        if (recursive)
            msg << " with recursive option";
        LogManager::getSingleton().logMessage(msg.str());

    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::removeResourceLocation(const String& name,
        const String& resGroup)
    {
        ResourceGroup* grp = getResourceGroup(resGroup, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // Remove from location list
        auto liend = grp->locationList.end();
        for (auto li = grp->locationList.begin(); li != liend; ++li)
        {
            Archive* pArch = li->archive;
            if (pArch->getName() == name)
            {
                grp->removeFromIndex(pArch);
                grp->locationList.erase(li);
                ArchiveManager::getSingleton().unload(pArch);
                break;
            }
        }

        LogManager::getSingleton().logMessage("Removed resource location " + name);

    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::declareResource(const String& name,
        const String& resourceType, const String& groupName,
        const NameValuePairList& loadParameters)
    {
        declareResource(name, resourceType, groupName, 0, loadParameters);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::declareResource(const String& name,
        const String& resourceType, const String& groupName,
        ManualResourceLoader* loader,
        const NameValuePairList& loadParameters)
    {
        ResourceGroup* grp = getResourceGroup(groupName, true);
        ResourceDeclaration dcl;
        dcl.loader = loader;
        dcl.parameters = loadParameters;
        dcl.resourceName = name;
        dcl.resourceType = resourceType;

        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
        grp->resourceDeclarations.push_back(dcl);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::undeclareResource(const String& name,
        const String& groupName)
    {
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        for (auto i = grp->resourceDeclarations.begin();
            i != grp->resourceDeclarations.end(); ++i)
        {
            if (i->resourceName == name)
            {
                grp->resourceDeclarations.erase(i);
                break;
            }
        }
    }
    //-----------------------------------------------------------------------
    DataStreamPtr ResourceGroupManager::openResourceImpl(const String& resourceName,
                                                     const String& groupName,
                                                     bool searchGroupsIfNotFound,
                                                     Resource* resourceBeingLoaded,
                                                     bool throwOnFailure) const
    {
        OgreAssert(!resourceName.empty(), "resourceName is empty string");
        if(mLoadingListener)
        {
            DataStreamPtr stream = mLoadingListener->resourceLoading(resourceName, groupName, resourceBeingLoaded);
            if(stream)
                return stream;
        }

        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, throwOnFailure);
        if (!grp)
        {
            // we only get here if throwOnFailure is false
            return DataStreamPtr();
        }

        Archive* pArch = resourceExists(grp, resourceName);

        if (pArch == NULL && (searchGroupsIfNotFound ||
            groupName == AUTODETECT_RESOURCE_GROUP_NAME || grp->inGlobalPool ||
            (!OGRE_RESOURCEMANAGER_STRICT && (groupName == DEFAULT_RESOURCE_GROUP_NAME))))
        {
            std::pair<Archive*, ResourceGroup*> ret = resourceExistsInAnyGroupImpl(resourceName);

            if(ret.second && resourceBeingLoaded && !grp->inGlobalPool) {
                resourceBeingLoaded->changeGroupOwnership(ret.second->name);
            }

            pArch = ret.first;
        }

        if (pArch)
        {
            DataStreamPtr stream = pArch->open(resourceName);
            if (mLoadingListener)
                mLoadingListener->resourceStreamOpened(resourceName, groupName, resourceBeingLoaded, stream);
            return stream;
        }

        if(!throwOnFailure)
            return DataStreamPtr();

        OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, "Cannot locate resource " +
            resourceName + " in resource group " + groupName + ".",
            "ResourceGroupManager::openResource");

    }
    //-----------------------------------------------------------------------
    DataStreamList ResourceGroupManager::openResources(
        const String& pattern, const String& groupName) const
    {
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // Iterate through all the archives and build up a combined list of
        // streams
        DataStreamList ret;

        LocationList::iterator li, liend;
        liend = grp->locationList.end();
        for (li = grp->locationList.begin(); li != liend; ++li)
        {
            Archive* arch = li->archive;
            // Find all the names based on whether this archive is recursive
            StringVectorPtr names = arch->find(pattern, li->recursive);

            // Iterate over the names and load a stream for each
            for (auto & ni : *names)
            {
                DataStreamPtr ptr = arch->open(ni);
                if (ptr)
                {
                    ret.push_back(ptr);
                }
            }
        }
        return ret;

    }
    //---------------------------------------------------------------------
    DataStreamPtr ResourceGroupManager::createResource(const String& filename,
        const String& groupName, bool overwrite, const String& locationPattern)
    {
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_AUTO_MUTEX;
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        for (LocationList::iterator li = grp->locationList.begin();
            li != grp->locationList.end(); ++li)
        {
            Archive* arch = li->archive;

            if (!arch->isReadOnly() &&
                (locationPattern.empty() || StringUtil::match(arch->getName(), locationPattern, false)))
            {
                if (!overwrite && arch->exists(filename))
                    OGRE_EXCEPT(Exception::ERR_DUPLICATE_ITEM,
                        "Cannot overwrite existing file " + filename,
                        "ResourceGroupManager::createResource");

                // create it
                DataStreamPtr ret = arch->create(filename);
                grp->addToIndex(filename, arch);

                return ret;
            }
        }

        OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
            "Cannot find a writable location in group " + groupName,
            "ResourceGroupManager::createResource");

    }
    //---------------------------------------------------------------------
    void ResourceGroupManager::deleteResource(const String& filename, const String& groupName,
        const String& locationPattern)
    {
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_AUTO_MUTEX;
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        for (LocationList::iterator li = grp->locationList.begin();
            li != grp->locationList.end(); ++li)
        {
            Archive* arch = li->archive;

            if (!arch->isReadOnly() &&
                (locationPattern.empty() || StringUtil::match(arch->getName(), locationPattern, false)))
            {
                if (arch->exists(filename))
                {
                    arch->remove(filename);
                    grp->removeFromIndex(filename, arch);

                    // only remove one file
                    break;
                }
            }
        }

    }
    //---------------------------------------------------------------------
    void ResourceGroupManager::deleteMatchingResources(const String& filePattern,
        const String& groupName, const String& locationPattern)
    {
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        for (LocationList::iterator li = grp->locationList.begin();
            li != grp->locationList.end(); ++li)
        {
            Archive* arch = li->archive;

            if (!arch->isReadOnly() &&
                (locationPattern.empty() || StringUtil::match(arch->getName(), locationPattern, false)))
            {
                StringVectorPtr matchingFiles = arch->find(filePattern);
                for (auto & f : *matchingFiles)
                {
                    arch->remove(f);
                    grp->removeFromIndex(f, arch);

                }
            }
        }

    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::addResourceGroupListener(ResourceGroupListener* l)
    {
        OGRE_LOCK_AUTO_MUTEX;

        mResourceGroupListenerList.push_back(l);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::removeResourceGroupListener(ResourceGroupListener* l)
    {
        OGRE_LOCK_AUTO_MUTEX;

        for (ResourceGroupListenerList::iterator i = mResourceGroupListenerList.begin();
            i != mResourceGroupListenerList.end(); ++i)
        {
            if (*i == l)
            {
                mResourceGroupListenerList.erase(i);
                break;
            }
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_registerResourceManager(
        const String& resourceType, ResourceManager* rm)
    {
        OGRE_LOCK_AUTO_MUTEX;

        LogManager::getSingleton().logMessage(
            "Registering ResourceManager for type " + resourceType);
        mResourceManagerMap[resourceType] = rm;
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_unregisterResourceManager(
        const String& resourceType)
    {
        OGRE_LOCK_AUTO_MUTEX;

        LogManager::getSingleton().logMessage(
            "Unregistering ResourceManager for type " + resourceType);

        ResourceManagerMap::iterator i = mResourceManagerMap.find(resourceType);
        if (i != mResourceManagerMap.end())
        {
            mResourceManagerMap.erase(i);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_registerScriptLoader(ScriptLoader* su)
    {
            OGRE_LOCK_AUTO_MUTEX;

        mScriptLoaderOrderMap.emplace(su->getLoadingOrder(), su);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_unregisterScriptLoader(ScriptLoader* su)
    {
            OGRE_LOCK_AUTO_MUTEX;

        Real order = su->getLoadingOrder();
        ScriptLoaderOrderMap::iterator oi = mScriptLoaderOrderMap.find(order);
        while (oi != mScriptLoaderOrderMap.end() && oi->first == order)
        {
            if (oi->second == su)
            {
                // erase does not invalidate on multimap, except current
                ScriptLoaderOrderMap::iterator del = oi++;
                mScriptLoaderOrderMap.erase(del);
            }
            else
            {
                ++oi;
            }
        }
    }
    //-----------------------------------------------------------------------
    ScriptLoader *ResourceGroupManager::_findScriptLoader(const String &pattern) const
    {
        OGRE_LOCK_AUTO_MUTEX;

        ScriptLoaderOrderMap::const_iterator oi;
        for (oi = mScriptLoaderOrderMap.begin();
            oi != mScriptLoaderOrderMap.end(); ++oi)
        {
            ScriptLoader* su = oi->second;
            const StringVector& patterns = su->getScriptPatterns();

            // Search for matches in the patterns
            for (const auto & p : patterns)
            {
                if(p == pattern)
                    return su;
            }
        }

        return 0; // No loader was found
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::parseResourceGroupScripts(ResourceGroup* grp) const
    {

        LogManager::getSingleton().logMessage(
            "Parsing scripts for resource group " + grp->name);

        // Count up the number of scripts we have to parse
        typedef std::pair<ScriptLoader*, FileInfoList> LoaderFileListPair;
        typedef std::vector<LoaderFileListPair> ScriptLoaderFileList;
        ScriptLoaderFileList scriptLoaderFileList;
        size_t scriptCount = 0;
        // Iterate over script users in loading order and get streams
        ScriptLoaderOrderMap::const_iterator oi;
        for (oi = mScriptLoaderOrderMap.begin();
            oi != mScriptLoaderOrderMap.end(); ++oi)
        {
            ScriptLoader* su = oi->second;

            scriptLoaderFileList.push_back(LoaderFileListPair(su, FileInfoList()));

            // Get all the patterns and search them
            const StringVector& patterns = su->getScriptPatterns();
            for (const auto & pattern : patterns)
            {
                FileInfoListPtr fileList = findResourceFileInfo(grp->name, pattern);
                FileInfoList& lst = scriptLoaderFileList.back().second;
                lst.insert(lst.end(), fileList->begin(), fileList->end());
            }

            scriptCount += scriptLoaderFileList.back().second.size();
        }
        // Fire scripting event
        fireResourceGroupScriptingStarted(grp->name, scriptCount);

        // Iterate over scripts and parse
        // Note we respect original ordering
        for (auto & slfli : scriptLoaderFileList)
        {
            ScriptLoader* su = slfli.first;
            // Iterate over each item in the list
            for (auto & fii : slfli.second)
            {
                bool skipScript = false;
                fireScriptStarted(fii.filename, skipScript);
                if(skipScript)
                {
                    LogManager::getSingleton().logMessage(
                        "Skipping script " + fii.filename);
                }
                else
                {
                    LogManager::getSingleton().logMessage(
                        "Parsing script " + fii.filename);
                    DataStreamPtr stream = fii.archive->open(fii.filename);
                    if (stream)
                    {
                        if (mLoadingListener)
                            mLoadingListener->resourceStreamOpened(fii.filename, grp->name, 0, stream);

                        if(fii.archive->getType() == "FileSystem" && stream->size() <= 1024 * 1024)
                        {
                            DataStreamPtr cachedCopy(OGRE_NEW MemoryDataStream(stream->getName(), stream));
                            su->parseScript(cachedCopy, grp->name);
                        }
                        else
                            su->parseScript(stream, grp->name);
                    }
                }
                fireScriptEnded(fii.filename, skipScript);
            }
        }

        fireResourceGroupScriptingEnded(grp->name);
        LogManager::getSingleton().logMessage(
            "Finished parsing scripts for resource group " + grp->name);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::createDeclaredResources(ResourceGroup* grp)
    {

        for (ResourceDeclarationList::iterator i = grp->resourceDeclarations.begin();
            i != grp->resourceDeclarations.end(); ++i)
        {
            ResourceDeclaration& dcl = *i;
            // Retrieve the appropriate manager
            ResourceManager* mgr = _getResourceManager(dcl.resourceType);
            // Create the resource
            ResourcePtr res = mgr->createResource(dcl.resourceName, grp->name,
                dcl.loader != 0, dcl.loader, &dcl.parameters);
            // Add resource to load list
            ResourceGroup::LoadResourceOrderMap::iterator li =
                grp->loadResourceOrderMap.find(mgr->getLoadingOrder());

            if (li == grp->loadResourceOrderMap.end())
            {
                grp->loadResourceOrderMap[mgr->getLoadingOrder()] = LoadUnloadResourceList();
            }
        }

    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_notifyResourceCreated(ResourcePtr& res) const
    {
        if (mCurrentGroup && res->getGroup() == mCurrentGroup->name)
        {
            // Use current group (batch loading)
            addCreatedResource(res, *mCurrentGroup);
        }
        else
        {
            // Find group
            ResourceGroup* grp = getResourceGroup(res->getGroup());
            if (grp)
            {
                addCreatedResource(res, *grp);
            }
        }

        fireResourceCreated(res);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_notifyResourceRemoved(const ResourcePtr& res) const
    {
        fireResourceRemove(res);

        if (mCurrentGroup && res->getGroup() == mCurrentGroup->name)
        {
            // Do nothing - we're batch unloading so list will be cleared
        }
        else
        {
            // Find group
            ResourceGroup* grp = getResourceGroup(res->getGroup());
            if (grp)
            {
                            OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
                ResourceGroup::LoadResourceOrderMap::iterator i =
                    grp->loadResourceOrderMap.find(
                        res->getCreator()->getLoadingOrder());
                if (i != grp->loadResourceOrderMap.end())
                {
                    // Iterate over the resource list and remove
                    LoadUnloadResourceList& resList = i->second;
                    for (LoadUnloadResourceList::iterator l = resList.begin();
                        l != resList.end(); ++ l)
                    {
                        if ((*l).get() == res.get())
                        {
                            // this is the one
                            resList.erase(l);
                            break;
                        }
                    }
                }
            }
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_notifyResourceGroupChanged(const String& oldGroup,
        Resource* res) const
    {
        ResourcePtr resPtr;

        // find old entry
        ResourceGroup* grp = getResourceGroup(oldGroup);

        if (grp)
        {
                    OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

            Real order = res->getCreator()->getLoadingOrder();
            ResourceGroup::LoadResourceOrderMap::iterator i =
                grp->loadResourceOrderMap.find(order);
            assert(i != grp->loadResourceOrderMap.end());
            LoadUnloadResourceList& loadList = i->second;
            for (LoadUnloadResourceList::iterator l = loadList.begin();
                l != loadList.end(); ++l)
            {
                if ((*l).get() == res)
                {
                    resPtr = *l;
                    loadList.erase(l);
                    break;
                }
            }
        }

        if (resPtr)
        {
            // New group
            ResourceGroup* newGrp = getResourceGroup(res->getGroup());

            addCreatedResource(resPtr, *newGrp);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_notifyAllResourcesRemoved(ResourceManager* manager) const
    {
            OGRE_LOCK_AUTO_MUTEX;

        // Iterate over all groups
        for (const auto & grpi : mResourceGroupMap)
        {
                    OGRE_LOCK_MUTEX(grpi->second->OGRE_AUTO_MUTEX_NAME);
            // Iterate over all priorities
            for (auto & oi : grpi.second->loadResourceOrderMap)
            {
                // Iterate over all resources
                for (LoadUnloadResourceList::iterator l = oi.second.begin();
                    l != oi.second.end(); )
                {
                    if ((*l)->getCreator() == manager)
                    {
                        // Increment first since iterator will be invalidated
                        LoadUnloadResourceList::iterator del = l++;
                        oi.second.erase(del);
                    }
                    else
                    {
                        ++l;
                    }
                }
            }

        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::addCreatedResource(ResourcePtr& res, ResourceGroup& grp) const
    {
            OGRE_LOCK_MUTEX(grp.OGRE_AUTO_MUTEX_NAME);
        Real order = res->getCreator()->getLoadingOrder();

        ResourceGroup::LoadResourceOrderMap::iterator i = grp.loadResourceOrderMap.find(order);
        LoadUnloadResourceList& loadList =
            i == grp.loadResourceOrderMap.end() ? grp.loadResourceOrderMap[order] : i->second;

        loadList.push_back(res);
    }
    //-----------------------------------------------------------------------
    ResourceGroupManager::ResourceGroup* ResourceGroupManager::getResourceGroup(const String& name,
                                                                                bool throwOnFailure) const
    {
        OGRE_LOCK_AUTO_MUTEX;
        ResourceGroupMap::const_iterator i = mResourceGroupMap.find(name);

        if (i == mResourceGroupMap.end())
        {
            if (throwOnFailure)
                OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "Cannot locate a resource group called '" + name + "'");

            return nullptr;
        }

        return i->second;
    }
    //-----------------------------------------------------------------------
    ResourceManager* ResourceGroupManager::_getResourceManager(const String& resourceType) const
    {
        OGRE_LOCK_AUTO_MUTEX;

        ResourceManagerMap::const_iterator i = mResourceManagerMap.find(resourceType);
        if (i == mResourceManagerMap.end())
        {
            OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
                "Cannot locate resource manager for resource type '" +
                resourceType + "'", "ResourceGroupManager::_getResourceManager");
        }
        return i->second;

    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::dropGroupContents(ResourceGroup* grp)
    {
            OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME);

        bool groupSet = false;
        if (!mCurrentGroup)
        {
            // Set current group to indicate ignoring of notifications
            mCurrentGroup = grp;
            groupSet = true;
        }
        // delete all the load list entries
        ResourceGroup::LoadResourceOrderMap::iterator j, jend;
        jend = grp->loadResourceOrderMap.end();
        for (j = grp->loadResourceOrderMap.begin(); j != jend; ++j)
        {
            // Iterate over resources
            for (auto & k : j->second)
            {
                k->getCreator()->remove(k);
            }
        }
        grp->loadResourceOrderMap.clear();

        if (groupSet)
        {
            mCurrentGroup = 0;
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::deleteGroup(ResourceGroup* grp)
    {
        {
            OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME);
            // delete all the load list entries
            grp->loadResourceOrderMap.clear();
        }

        // delete ResourceGroup
        OGRE_DELETE_T(grp, ResourceGroup, MEMCATEGORY_RESOURCE);
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceGroupScriptingStarted(const String& groupName, size_t scriptCount) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceGroupScriptingStarted(groupName, scriptCount);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireScriptStarted(const String& scriptName, bool &skipScript) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            bool temp = false;
            l->scriptParseStarted(scriptName, temp);
            if(temp)
                skipScript = true;
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireScriptEnded(const String& scriptName, bool skipped) const
    {
        OGRE_LOCK_AUTO_MUTEX;
            for (auto l : mResourceGroupListenerList)
            {
                l->scriptParseEnded(scriptName, skipped);
            }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceGroupScriptingEnded(const String& groupName) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceGroupScriptingEnded(groupName);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceGroupLoadStarted(const String& groupName, size_t resourceCount) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceGroupLoadStarted(groupName, resourceCount);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceLoadStarted(const ResourcePtr& resource) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceLoadStarted(resource);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceLoadEnded(void) const
    {
        OGRE_LOCK_AUTO_MUTEX;
            for (auto l : mResourceGroupListenerList)
            {
                l->resourceLoadEnded();
            }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_notifyCustomStageStarted(const String& desc) const
    {
        OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->customStageStarted(desc);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::_notifyCustomStageEnded(void) const
    {
        OGRE_LOCK_AUTO_MUTEX;
            for (auto l : mResourceGroupListenerList)
            {
                l->customStageEnded();
            }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceGroupLoadEnded(const String& groupName) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceGroupLoadEnded(groupName);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceGroupPrepareStarted(const String& groupName, size_t resourceCount) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceGroupPrepareStarted(groupName, resourceCount);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourcePrepareStarted(const ResourcePtr& resource) const
    {
        OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourcePrepareStarted(resource);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourcePrepareEnded(void) const
    {
        OGRE_LOCK_AUTO_MUTEX;
            for (auto l : mResourceGroupListenerList)
            {
                l->resourcePrepareEnded();
            }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceGroupPrepareEnded(const String& groupName) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceGroupPrepareEnded(groupName);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceCreated(const ResourcePtr& resource) const
    {
        OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceCreated(resource);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::fireResourceRemove(const ResourcePtr& resource) const
    {
        OGRE_LOCK_AUTO_MUTEX;
        for (auto l : mResourceGroupListenerList)
        {
            l->resourceRemove(resource);
        }
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::shutdownAll(void)
    {
        OGRE_LOCK_AUTO_MUTEX;

        ResourceManagerMap::iterator i, iend;
        iend = mResourceManagerMap.end();
        for (i = mResourceManagerMap.begin(); i != iend; ++i)
        {
            i->second->removeAll();
        }
    }
    //-----------------------------------------------------------------------
    StringVectorPtr ResourceGroupManager::listResourceNames(const String& groupName, bool dirs) const
    {
        auto vec = std::make_shared<StringVector>();

        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // Iterate over the archives
        LocationList::iterator i, iend;
        iend = grp->locationList.end();
        for (i = grp->locationList.begin(); i != iend; ++i)
        {
            StringVectorPtr lst = i->archive->list(i->recursive, dirs);
            vec->insert(vec->end(), lst->begin(), lst->end());
        }

        return vec;

    }
    //-----------------------------------------------------------------------
    FileInfoListPtr ResourceGroupManager::listResourceFileInfo(const String& groupName, bool dirs) const
    {
        auto vec = std::make_shared<FileInfoList>();

        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // Iterate over the archives
        LocationList::iterator i, iend;
        iend = grp->locationList.end();
        for (i = grp->locationList.begin(); i != iend; ++i)
        {
            FileInfoListPtr lst = i->archive->listFileInfo(i->recursive, dirs);
            vec->insert(vec->end(), lst->begin(), lst->end());
        }

        return vec;

    }
    //-----------------------------------------------------------------------
    StringVectorPtr ResourceGroupManager::findResourceNames(const String& groupName,
        const String& pattern, bool dirs) const
    {
        auto vec = std::make_shared<StringVector>();

        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

            // Iterate over the archives
            LocationList::iterator i, iend;
        iend = grp->locationList.end();
        for (i = grp->locationList.begin(); i != iend; ++i)
        {
            StringVectorPtr lst = i->archive->find(pattern, i->recursive, dirs);
            vec->insert(vec->end(), lst->begin(), lst->end());
        }

        return vec;
    }
    //-----------------------------------------------------------------------
    FileInfoListPtr ResourceGroupManager::findResourceFileInfo(const String& groupName,
        const String& pattern, bool dirs) const
    {
        auto vec = std::make_shared<FileInfoList>();

        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

            // Iterate over the archives
            LocationList::iterator i, iend;
        iend = grp->locationList.end();
        for (i = grp->locationList.begin(); i != iend; ++i)
        {
            FileInfoListPtr lst = i->archive->findFileInfo(pattern, i->recursive, dirs);
            vec->insert(vec->end(), lst->begin(), lst->end());
        }

        return vec;
    }
    //-----------------------------------------------------------------------
    bool ResourceGroupManager::resourceExists(const String& groupName, const String& resourceName) const
    {
        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        return resourceExists(grp, resourceName) != 0;
    }
    //-----------------------------------------------------------------------
    Archive* ResourceGroupManager::resourceExists(ResourceGroup* grp, const String& resourceName) const
    {

            OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // Try indexes first
        ResourceLocationIndex::iterator rit = grp->resourceIndexCaseSensitive.find(resourceName);
        if (rit != grp->resourceIndexCaseSensitive.end())
        {
            // Found in the index
            return rit->second;
        }

#if !OGRE_RESOURCEMANAGER_STRICT
        // try case insensitive
        String lcResourceName = resourceName;
        StringUtil::toLowerCase(lcResourceName);
        rit = grp->resourceIndexCaseInsensitive.find(lcResourceName);
        if (rit != grp->resourceIndexCaseInsensitive.end())
        {
            // Found in the index
            return rit->second;
        }

        // Search the hard way
        LocationList::iterator li, liend;
        liend = grp->locationList.end();
        for (li = grp->locationList.begin(); li != liend; ++li)
        {
            Archive* arch = li->archive;
            if (arch->exists(resourceName))
            {
                return arch;
            }
        }
#endif

        return NULL;

    }
    //-----------------------------------------------------------------------
    time_t ResourceGroupManager::resourceModifiedTime(const String& groupName, const String& resourceName) const
    {
        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        return resourceModifiedTime(grp, resourceName);
    }
    //-----------------------------------------------------------------------
    time_t ResourceGroupManager::resourceModifiedTime(ResourceGroup* grp, const String& resourceName) const
    {
        Archive* arch = resourceExists(grp, resourceName);
        if (arch)
        {
            return arch->getModifiedTime(resourceName);
        }

        return 0;
    }
    //-----------------------------------------------------------------------
    std::pair<Archive*, ResourceGroupManager::ResourceGroup*>
    ResourceGroupManager::resourceExistsInAnyGroupImpl(const String& filename) const
    {
        OgreAssert(!filename.empty(), "resourceName is empty string");
            OGRE_LOCK_AUTO_MUTEX;

            // Iterate over resource groups and find
        for (const auto & i : mResourceGroupMap)
        {
            Archive* arch = resourceExists(i.second, filename);
            if (arch)
                return std::make_pair(arch, i.second);
        }
        // Not found
        return std::pair<Archive*, ResourceGroup*>();
    }
    //-----------------------------------------------------------------------
    bool ResourceGroupManager::resourceExistsInAnyGroup(const String& filename) const
    {
        return resourceExistsInAnyGroupImpl(filename).first != 0;
    }
    //-----------------------------------------------------------------------
    const String& ResourceGroupManager::findGroupContainingResource(const String& filename) const
    {
        ResourceGroup* grp = resourceExistsInAnyGroupImpl(filename).second;

        if(grp)
            return grp->name;

        OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
            "Unable to derive resource group for " +
            filename + " automatically since the resource was not "
            "found.",
            "ResourceGroupManager::findGroupContainingResource");
    }
    //-----------------------------------------------------------------------
    StringVectorPtr ResourceGroupManager::listResourceLocations(const String& groupName) const
    {
        auto vec = std::make_shared<StringVector>();

        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // Iterate over the archives
        LocationList::iterator i, iend;
        iend = grp->locationList.end();
        for (i = grp->locationList.begin(); i != iend; ++i)
        {
            vec->push_back(i->archive->getName());
        }

        return vec;
    }
    //-----------------------------------------------------------------------
    StringVectorPtr ResourceGroupManager::findResourceLocation(const String& groupName, const String& pattern) const
    {
        auto vec = std::make_shared<StringVector>();

        // Try to find in resource index first
        ResourceGroup* grp = getResourceGroup(groupName, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex

        // Iterate over the archives
        LocationList::iterator i, iend;
        iend = grp->locationList.end();
        for (i = grp->locationList.begin(); i != iend; ++i)
        {
            String location = i->archive->getName();
            // Search for the pattern
            if(StringUtil::match(location, pattern))
            {
                vec->push_back(location);
            }
        }

        return vec;
    }
    //-----------------------------------------------------------------------
    void ResourceGroupManager::setCustomStagesForResourceGroup(const String& group, uint32 stageCount)
    {
        ResourceGroup* grp = getResourceGroup(group, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
        grp->customStageCount = stageCount;
    }
    //-----------------------------------------------------------------------
    uint32 ResourceGroupManager::getCustomStagesForResourceGroup(const String& group)
    {
        ResourceGroup* grp = getResourceGroup(group, true);
        OGRE_LOCK_MUTEX(grp->OGRE_AUTO_MUTEX_NAME); // lock group mutex
        return grp->customStageCount;
    }
    //-----------------------------------------------------------------------
    bool ResourceGroupManager::isResourceGroupInGlobalPool(const String& name) const
    {
        return getResourceGroup(name, true)->inGlobalPool;
    }
    //-----------------------------------------------------------------------
    StringVector ResourceGroupManager::getResourceGroups(void) const
    {
            OGRE_LOCK_AUTO_MUTEX;
        StringVector vec;
        for (const auto & i : mResourceGroupMap)
        {
            vec.push_back(i.second->name);
        }
        return vec;
    }
    //-----------------------------------------------------------------------
    ResourceGroupManager::ResourceDeclarationList
    ResourceGroupManager::getResourceDeclarationList(const String& group) const
    {
        return getResourceGroup(group, true)->resourceDeclarations;
    }
    //---------------------------------------------------------------------
    const ResourceGroupManager::LocationList&
    ResourceGroupManager::getResourceLocationList(const String& group) const
    {
        return getResourceGroup(group, true)->locationList;
    }
    //-------------------------------------------------------------------------
    void ResourceGroupManager::setLoadingListener(ResourceLoadingListener *listener)
    {
        mLoadingListener = listener;
    }
    //-------------------------------------------------------------------------
    ResourceLoadingListener *ResourceGroupManager::getLoadingListener() const
    {
        return mLoadingListener;
    }
    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    void ResourceGroupManager::ResourceGroup::addToIndex(const String& filename, Archive* arch)
    {
        // internal, assumes mutex lock has already been obtained
        this->resourceIndexCaseSensitive.emplace(filename, arch);

#if !OGRE_RESOURCEMANAGER_STRICT
        if (!arch->isCaseSensitive())
        {
            String lcase = filename;
            StringUtil::toLowerCase(lcase);
            this->resourceIndexCaseInsensitive.emplace(lcase, arch);
        }
#endif
    }
    //---------------------------------------------------------------------
    void ResourceGroupManager::ResourceGroup::removeFromIndex(const String& filename, Archive* arch)
    {
        // internal, assumes mutex lock has already been obtained
        ResourceLocationIndex::iterator i = this->resourceIndexCaseSensitive.find(filename);
        if (i != this->resourceIndexCaseSensitive.end() && i->second == arch)
            this->resourceIndexCaseSensitive.erase(i);

#if !OGRE_RESOURCEMANAGER_STRICT
        if (!arch->isCaseSensitive())
        {
            String lcase = filename;
            StringUtil::toLowerCase(lcase);
            i = this->resourceIndexCaseInsensitive.find(lcase);
            if (i != this->resourceIndexCaseInsensitive.end() && i->second == arch)
                this->resourceIndexCaseInsensitive.erase(i);
        }
#endif
    }
    //---------------------------------------------------------------------
    void ResourceGroupManager::ResourceGroup::removeFromIndex(Archive* arch)
    {
        // Delete indexes
        ResourceLocationIndex::iterator rit, ritend;
#if !OGRE_RESOURCEMANAGER_STRICT
        ritend = this->resourceIndexCaseInsensitive.end();
        for (rit = this->resourceIndexCaseInsensitive.begin(); rit != ritend;)
        {
            if (rit->second == arch)
            {
                ResourceLocationIndex::iterator del = rit++;
                this->resourceIndexCaseInsensitive.erase(del);
            }
            else
            {
                ++rit;
            }
        }
#endif
        ritend = this->resourceIndexCaseSensitive.end();
        for (rit = this->resourceIndexCaseSensitive.begin(); rit != ritend;)
        {
            if (rit->second == arch)
            {
                ResourceLocationIndex::iterator del = rit++;
                this->resourceIndexCaseSensitive.erase(del);
            }
            else
            {
                ++rit;
            }
        }

    }
}


text:
require("Logger")

local AppSetupNotificationRequest = require("app.request.AppSetupNotificationRequest")

local Manager = Class()

function Manager.new(self)
    local bridge
    local _error

    function self.init(_bridge)
        bridge = _bridge
    end

    function self.setDelegate(delegate)
        bridge.setDelegate(delegate)
    end

    function self.getNotifications()
        Log.d("app.Manager.getNotifications(): Getting notifications")
        local response = bridge.getNotifications()
        if response.isSuccess() then
            Log.d("app.Manager.getNotifications(): Successfully queried notifications")
            return response.getNotifications()
        end
        Log.d("app.Manager.getNotifications(): Failed to get notifications")
        _error = response.getError()
        return 0
    end

    function self.setupNotification(message, interval)
        Log.d("app.Manager.getNotifications(): Setting up notifications w/ message (%s) interval (%s)", message, interval)
        local response = bridge.setupNotification(AppSetupNotificationRequest(message, interval))
        if response.isSuccess() then
            return true
        end
        _error = response.getError()
        return false
    end

    function self.getError()
        return _error
    end
end

return Manager

redpajama_set_name: RedPajamaGithub


text:
package com.wanjian.sak.system.input;

import android.view.InputEvent;

public interface InputEventListener {

  boolean onBeforeInputEvent(InputEvent inputEvent);

  void onAfterInputEvent(InputEvent inputEvent);
}


text:
Package.describe({
    name: 'elmarti:video-chat',
    version: '2.3.2',
    summary: 'Simple WebRTC Video Chat for your app.',
    git: 'https://github.com/elmarti/meteor-video-chat',
    documentation: 'README.md'
});

Package.onUse(api => {

    Npm.depends({
        "rtcfly": "0.1.8"
    });

    api.versionsFrom('1.5');
    api.use('ecmascript');
    api.use('reactive-var');
    api.use("rocketchat:streamer@0.6.2");
    api.use("mizzao:user-status@0.6.7");
    api.addFiles(['lib/publish.js'], "server");
    api.addFiles(['lib/index.server.js'], 'server');
    api.mainModule('meteor.js', 'client');
    api.mainModule('lib/server.interface.js', 'server');
});


text:
"""
    templatetricks.link_form_macro
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    link_to and form_tag macros
    http://flask.pocoo.org/snippets/14/
"""

import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(os.path.dirname(__file__))))

from app import app

"""
The HTML/Jinja for a link to a page on your site looks quite awkward.

Link text!
With this macro:

{% macro link_to(endpoint, text) -%}
{{ text|safe }}
{%- endmacro %}
The above link will become:

{{ link_to('some.view', "Link text!", foo=bar) }}
It's shorter and looks cleaner, especially if the content of the link is also a variable.

A similar technique, using the call tag, can be used for forms:

{% macro form_tag(endpoint, method='post') -%}
<form action="{{ url_for(endpoint, **kwargs) }}"
      method="{{ method }}">
  {{ caller () }}
</form>
{%- endmacro %}
Then, you can create a form with:

{% call form_tag('create_entry') %}

<input type=text name=whatever>

<input type=submit value=Submit>

{% endcall %}
"""


text:
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [0.8.8] - 2017-07-11
### Fixed
- Bug in cleanup code (would remove intermediate directories)

## [0.8.7] - 2017-07-07
### Fixed
- Infinite caching for metadata.

## [0.8.6] - 2017-06-15
### Fixed
- Infinite caching for metadata.

## [0.8.5] - 2017-06-15
### Fixed
- Fixed the time info of empty timeseries
- Fix error handling in repair()/clean()

### New
- Added bgutil stats
- Better caching for find()
- Use new Graphite-Web API for FindQuery
- Make read_on/updated_on more configurable

## [0.8.4] - 2017-05-15
### Fixed
- Better logging

### New
- Add `bgutil copy`
- Better exception in carbon.py when metric can't be found

## [0.8.3] - 2017-04-25
- Nothing, just an issue with pypi uploads.

## [0.8.2] - 2017-04-25
### Fixed
- Fix ipython and cassandra versions

## [0.8.1] - 2017-04-05
### Fixed
- Better handling of Cassandra exceptions
- Better handling of carbonlink
- Fix metric reporting interval

## [0.8.0] - 2017-03-10
### Added
- Configurable consistency settings (#242)
- [BREAKING] New column (read_on) in metric_metadata for adding statistics on read metrics (#107)
  need to run the following cql to migrate theschema
```SQL
   ALTER TABLE biggraphite_metadata.metrics_metadata
   ADD read_on timeuuid;
```
- Added a tool to replay graphite traffic
- Support Carbon Link protocol

### Fixed
- Fix multi-cluster support
- Be more robust when metadata is missing

### Changed
- Added some randomness to cache expiration

## [0.7.0]
### Added
- Prometheus support and additional metrics
- Add support for multiple replicas/writers
- Microbenchmarks
- Asynchronous creation of metrics in carbon
- Better bg-import-whisper / bg-generate-sstables + helper scripts

### Fixed
- Multiple bugs with the disk cache
- Multiple clean/repair bugs

### Changed
- [Breaking] Cassandra schema as been changed to separate stage0 and aggregated
  metrics. There is no upgrade procedure as this is a pre-release.
- [Breaking] `bgutil syncdb` needs to be run during initial install

## [0.6.0] - 2016-11-25
### Added
- Initial release with this CHANGELOG file

### Changed
- We are going to do releases from now on

[Unreleased]: https://github.com/criteo/biggraphite/compare/v0.8.8...HEAD
[0.8.7]: https://github.com/criteo/biggraphite/compare/v0.8.7...v0.8.8
[0.8.6]: https://github.com/criteo/biggraphite/compare/v0.8.6...v0.8.7
[0.8.5]: https://github.com/criteo/biggraphite/compare/v0.8.5...v0.8.6
[0.8.5]: https://github.com/criteo/biggraphite/compare/v0.8.4...v0.8.5
[0.8.4]: https://github.com/criteo/biggraphite/compare/v0.8.3...v0.8.4
[0.8.3]: https://github.com/criteo/biggraphite/compare/v0.8.2...v0.8.3
[0.8.2]: https://github.com/criteo/biggraphite/compare/v0.8.1...v0.8.2
[0.8.1]: https://github.com/criteo/biggraphite/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/criteo/biggraphite/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/criteo/biggraphite/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/criteo/biggraphite/compare/v0.6.0


text:
import unittest

from IECore import *

class PointsMotionOpTest( unittest.TestCase ) :

	def _buildPoints( self, time ):
		p = PointsPrimitive( 5 )
		p[ "P" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Vertex, V3fVectorData( [ V3f(time*1), V3f(time*2), V3f(time*3), V3f(time*4), V3f(time*5) ] ) )
		p[ "id" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Vertex, IntVectorData( [ 1, 2, 3, 4, 5 ] ) )
		p[ "float" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Varying, FloatVectorData( [ time*1, time*2, time*3, time*4, time*5 ] ) )
		p[ "vec3" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Vertex, V3fVectorData( [ V3f(time*2), V3f(time*3), V3f(time*4), V3f(time*5), V3f(time*6) ] ) )
		p[ "vec2" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Vertex, V2fVectorData( [ V2f(time*2), V2f(time*3), V2f(time*4), V2f(time*5), V2f(time*6) ] ) )
		p[ "C" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Vertex, Color3fVectorData( [ Color3f(1), Color3f(2), Color3f(4), Color3f(5), Color3f(6) ] ) )
		p[ "C2" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Vertex, Color4fVectorData( [ Color4f(1), Color4f(2), Color4f(4), Color4f(5), Color4f(6) ] ) )
		p[ "G" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Uniform, V3fVectorData( [ V3f(time) ] ) )
		p[ "c" ] = PrimitiveVariable( PrimitiveVariable.Interpolation.Constant, FloatData( time ) )
		return p

	def testConstruction( self ) :

		op = PointsMotionOp()

	def testPrimvarCopy( self ) :

		p1 = self._buildPoints( 1.0 )
		p2 = self._buildPoints( 2.0 )
		points = ObjectVector()
		points.append( p1 )
		points.append( p2 )

		op = PointsMotionOp()
		result = op( snapshotTimes = FloatVectorData( [ 1, 2 ] ), pointsPrimitives = points )
		# proves that the output correspond exactly to the input
		self.assertEqual( len(result), 2 )
		self.assertEqual( result[1], p1 )
		self.assertEqual( result[2], p2 )
		self.assertNotEqual( result[1], p2 )

		p1[ "P" ].data[3] = V3f(1,2,3)
		self.assertEqual( p1["P"].data[3], V3f(1,2,3) )
		# proves that destroying the original input value, keeps the output untouched.
		self.assertEqual( result[1]["P"].data[3], V3f(4) )

	def testInvalidInputParams( self ):

		p1 = self._buildPoints( 1.0 )
		p2 = self._buildPoints( 2.0 )
		points = ObjectVector()
		points.append( p1 )
		points.append( p2 )
		op = PointsMotionOp()
		op['pointsPrimitives'] = points

		# snapshots count != primitive count
		op['snapshotTimes'] = FloatVectorData( [ 1 ] )
		self.assertRaises( RuntimeError, op.__call__, **{} )

		# test no id
		op['snapshotTimes'] = FloatVectorData( [ 1, 2 ] )
		op['idPrimVarName'] = "dontExist"
		self.assertRaises( RuntimeError, op.__call__, **{} )
		op['idPrimVarName'] = "id"
		op()	# should work

		# test unmatching primvars
		del p1["vec2"]
		self.assertRaises( RuntimeError, op.__call__, **{} )
		del p2["vec2"]
		op()	# should work

		# test no P
		del p2["P"]
		self.assertRaises( RuntimeError, op.__call__, **{} )

	def testDifferentPointsOrder( self ):

		def rearrangeVec( vec ):
			lastIndex = len(vec) - 1
			for i in xrange(0,len(vec)/2 ):
				tmp = vec[i]
				vec[i] = vec[ lastIndex ]
				vec[ lastIndex ] = tmp
				lastIndex -= 1

		p1 = self._buildPoints( 1.0 )
		p2 = self._buildPoints( 1.0 )
		points = ObjectVector()
		points.append( p1 )
		points.append( p2 )
		op = PointsMotionOp()

		for primVar in p2.values() :
			if primVar.interpolation in [ PrimitiveVariable.Interpolation.Vertex, PrimitiveVariable.Interpolation.Varying, PrimitiveVariable.Interpolation.FaceVarying ] :
				rearrangeVec( primVar.data )

		self.assertNotEqual( p1, p2 )
		result = op( snapshotTimes = FloatVectorData( [ 1, 2 ] ), pointsPrimitives = points )
		self.assertEqual( result[1], p1 )
		self.assertEqual( result[2], p1 )

	def testSingleSnapshot( self ):

		p1 = self._buildPoints( 1.0 )
		points = ObjectVector()
		points.append( p1 )
		op = PointsMotionOp()
		result = op( snapshotTimes = FloatVectorData( [ 1 ] ), pointsPrimitives = points )
		self.assertEqual( len(result), 1 )
		self.assertEqual( result[1], p1 )

	def testNoSnapshots( self ):

		points = ObjectVector()
		op = PointsMotionOp()
		result = op( snapshotTimes = FloatVectorData( [] ), pointsPrimitives = points )
		self.assertEqual( len(result), 0 )

	def testMissingSnapshots( self ):

		p1 = self._buildPoints( 1.0 )
		p2 = self._buildPoints( 2.0 )
		p3 = self._buildPoints( 3.0 )
		p4 = self._buildPoints( 4.0 )
		p5 = self._buildPoints( 5.0 )
		points = ObjectVector()
		points.append( p1 )
		points.append( p2 )
		points.append( p3 )
		points.append( p4 )
		points.append( p5 )

		# change some ids to simulate dead particles, and birth of new particles
		p1["id"].data[0] = 10
		p1["id"].data[1] = 11
		p2["id"].data[1] = 11
		p4["id"].data[3] = 12
		p5["id"].data[3] = 12
		p5["id"].data[4] = 13

		op = PointsMotionOp()
		result = op( snapshotTimes = FloatVectorData( [ 1, 2, 3, 4, 5 ] ), pointsPrimitives = points, maskedPrimVars = StringVectorData( [ 'vec3' ] ) )

		self.assertEqual( len(result), 5 )
		# checking ids
		self.assertEqual( result[1]["id"].data, IntVectorData([ 10,11,3,4,5,1,2,12,13 ]) )
		self.assertEqual( result[1]["id"].data, result[2]["id"].data )
		self.assertEqual( result[1]["id"].data, result[3]["id"].data )
		self.assertEqual( result[1]["id"].data, result[4]["id"].data )
		self.assertEqual( result[1]["id"].data, result[5]["id"].data )
		# checking if unmasked prim var was filled with closest available value
		self.assertEqual( result[1]["P"].data, V3fVectorData( [ V3f(1*1), V3f(1*2), V3f(1*3), V3f(1*4), V3f(1*5),   V3f(2*1), V3f(3*2), V3f(4*4), V3f(5*5) ] ) )
		self.assertEqual( result[3]["P"].data, V3fVectorData( [ V3f(1*1), V3f(2*2), V3f(3*3), V3f(3*4), V3f(3*5),   V3f(3*1), V3f(3*2), V3f(4*4), V3f(5*5) ] ) )
		self.assertEqual( result[5]["P"].data, V3fVectorData( [ V3f(1*1), V3f(2*2), V3f(5*3), V3f(3*4), V3f(4*5),   V3f(5*1), V3f(5*2), V3f(5*4), V3f(5*5) ] ) )
		# checkin if masked prim var was filled with zero
		self.assertEqual( result[3]["vec3"].data, V3fVectorData( [ V3f(0), V3f(0), V3f(3*4), V3f(3*5), V3f(3*6),   V3f(3*2), V3f(3*3), V3f(0), V3f(0) ] ) )

if __name__ == "__main__":
    unittest.main()


text:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE locale SYSTEM "../../../../../lib/pkp/dtd/locale.dtd">

<locale name="de_DE" full_name="Deutsch (Deutschland)">
	<message key="plugins.generic.customBlockManager.displayName">Verwaltung eigener Blöcke</message>
	<message key="plugin.generic.customBlockManager.introduction"><![CDATA[Sie können dieses Plug-In benutzen, um eigene Block-Plug-Ins hinzuzufügen oder zu löschen. Sie können dann den Inhalt über die Seite System-Plug-Ins (im Abschnitt Block-Plug-Ins) bearbeiten. Um Ihr Plug-In am gewünschten Ort in der Sidebar zu platzieren, gehen Sie zu Schritt 5.6 des Setups.]]></message>
	<message key="plugins.generic.customBlockManager.settings">Einstellungen</message>
	<message key="plugins.generic.customBlockManager.description">Dieses Plug-In lässt Sie eigene Sidebar-Blöcke verwalten. Sie können die Blöcke in den Einstellungen jedes Plug-Ins, das Sie hier anlegen, verwalten.</message>
	<message key="plugins.generic.customBlockManager.blockName">Name des Blocks</message>
	<message key="plugins.generic.customBlockManager.action">Aktion</message>
	<message key="plugins.generic.customBlockManager.delete">Löschen</message>
	<message key="plugins.generic.customBlockManager.addBlock">Block hinzufügen</message>

	<message key="plugins.generic.customBlock.content">Inhalt</message>
	<message key="plugins.generic.customBlock.saved">Der Inhalt des Blocks wurde gespeichert.</message>
	<message key="plugins.generic.customBlock.nameSuffix">(Eigener-Block-Plug-In)</message>
	<message key="plugins.generic.customBlock.description">Dies ist ein selbst angelegter Block.</message>
	<message key="plugins.generic.customBlock.editContent">Bearbeitung des Inhalts der Sidebar</message>
	<message key="plugins.generic.customBlock.contentRequired">Irgendein Inhalt wird benötigt. Wenn Sie diesen Block löschen möchten, können Sie dies mittels des Block-Verwaltungs-Plug-Ins tun oder durch abwählen dieses Blocks in Schritt 5 des Zeitschriftensetups.</message>
	<message key="plugins.generic.customBlock.edit">Bearbeiten</message>
</locale>


text:
import React from "react";
import {
  BlockQuote,
  Quote,
  Cite
} from "spectacle";
import Enhance from './enhance';

const QuoteSlide = (props) => (

      <Quote textSize="1.5em">{props.quote}</Quote>
      {props.cite}

);

export default Enhance(QuoteSlide);


text:
package org.skyscreamer.jsonassert;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.text.MessageFormat;

import org.json.JSONException;
import org.junit.Test;
import org.skyscreamer.jsonassert.comparator.ArraySizeComparator;
import org.skyscreamer.jsonassert.comparator.CustomComparator;
import org.skyscreamer.jsonassert.comparator.DefaultComparator;
import org.skyscreamer.jsonassert.comparator.JSONComparator;

/**
 * Unit tests for ArrayValueMatcher
 *
 * @author Duncan Mackinder
 *
 */
public class ArrayValueMatcherTest {

	private static final String ARRAY_OF_JSONOBJECTS = "{a:[{background:white,id:1,type:row},{background:grey,id:2,type:row},{background:white,id:3,type:row},{background:grey,id:4,type:row}]}";
	private static final String ARRAY_OF_INTEGERS = "{a:[1,2,3,4,5]}";
	private static final String ARRAY_OF_JSONARRAYS = "{a:[[6,7,8],[9,10,11],[12,13,14],[19,20,21,22]]}";
	private static final JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);

	private void doTest(String jsonPath, ArrayValueMatcher<Object> arrayValueMatcher, String expectedJSON,
			String actualJSON) throws JSONException {
		Customization customization = new Customization(jsonPath, arrayValueMatcher);
		JSONAssert.assertEquals(expectedJSON, actualJSON, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	private void doFailingMatchTest(String jsonPath, ArrayValueMatcher<Object> arrayValueMatcher, String expectedJSON, String actualJSON, String expectedMessagePattern) throws JSONException {
		try {
			doTest(jsonPath, arrayValueMatcher, expectedJSON, actualJSON);
		}
		catch (AssertionError e) {
			String failureMessage = MessageFormat.format("Exception message ''{0}'', does not match expected pattern ''{1}''", e.getMessage(), expectedMessagePattern);
			assertTrue(failureMessage, e.getMessage().matches(expectedMessagePattern));
			return;
		}
		fail("AssertionError not thrown");
	}

	@Test
	public void matchesSecondElementOfJSONObjectArray() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator, 1), "{a:[{background:grey,id:2,type:row}]}", ARRAY_OF_JSONOBJECTS);
	}

	@Test
	public void failsWhenSecondElementOfJSONObjectArrayDoesNotMatch() throws JSONException {
		doFailingMatchTest("a",
				new ArrayValueMatcher<Object>(comparator, 1),
				"{a:[{background:DOES_NOT_MATCH,id:2,type:row}]}",
				ARRAY_OF_JSONOBJECTS,
				"a\\[1\\]\\.background\\s*Expected:\\s*DOES_NOT_MATCH\\s*got:\\s*grey\\s*");
	}

	@Test
	public void failsWhenThirdElementOfJSONObjectArrayDoesNotMatchInMultiplePlaces() throws JSONException {
		doFailingMatchTest("a",
				new ArrayValueMatcher<Object>(comparator, 2),
				"{a:[{background:DOES_NOT_MATCH,id:3,type:WRONG_TYPE}]}",
				ARRAY_OF_JSONOBJECTS,
				"a\\[2\\]\\.background\\s*Expected:\\s*DOES_NOT_MATCH\\s*got:\\s*white\\s*;\\s*a\\[2\\]\\.type\\s*Expected:\\s*WRONG_TYPE\\s*got:\\s*row\\s*");
	}

	@Test
	public void failsWhenTwoElementsOfJSONObjectArrayDoNotMatch() throws JSONException {
		doFailingMatchTest("a",
				new ArrayValueMatcher<Object>(comparator, 1, 2),
				"{a:[{background:DOES_NOT_MATCH,id:2,type:row},{background:white,id:3,type:WRONG_TYPE}]}",
				ARRAY_OF_JSONOBJECTS,
				"a\\[1\\]\\.background\\s*Expected:\\s*DOES_NOT_MATCH\\s*got:\\s*grey\\s*;\\s*a\\[2\\]\\.type\\s*Expected:\\s*WRONG_TYPE\\s*got:\\s*row\\s*");
	}

	@Test
	public void matchesThirdElementOfSimpleValueArray() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator, 2), "{a:[3]}", ARRAY_OF_INTEGERS);
	}

	@Test
	public void failsWhenTwoElementOfSimpleValueArrayDoNotMatch() throws JSONException {
		doFailingMatchTest("a", new ArrayValueMatcher<Object>(comparator, 3, 4), "{a:[3,4]}", ARRAY_OF_INTEGERS,
				"a\\[3\\]\\s*Expected:\\s3\\s*got:\\s*4\\s*;\\s*a\\[4\\]\\s*Expected:\\s*4\\s*got:\\s*5\\s*");
	}

	@Test
	public void matchesFirstElementOfArrayOfJSONArrays() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator, 0), "{a:[[6,7,8]]}", ARRAY_OF_JSONARRAYS);
	}

	@Test
	public void matchesSizeOfFirstThreeInnerArrays() throws JSONException {
		JSONComparator innerArraySizeComparator = new ArraySizeComparator(JSONCompareMode.STRICT_ORDER);
		doTest("a", new ArrayValueMatcher<Object>(innerArraySizeComparator, 0, 2), "{a:[[3]]}", ARRAY_OF_JSONARRAYS);
	}

	@Test
	public void failsWhenInnerArraySizeDoesNotMatch() throws JSONException {
		JSONComparator innerArraySizeComparator = new ArraySizeComparator(JSONCompareMode.STRICT_ORDER);
		doFailingMatchTest("a",
				new ArrayValueMatcher<Object>(innerArraySizeComparator),
				"{a:[[3]]}",
				ARRAY_OF_JSONARRAYS,
				"a\\[3\\]\\[\\]\\s*Expected:\\s*array size of 3 elements\\s*got:\\s*4 elements\\s*");
	}

	@Test
	public void failsWhenInnerJSONObjectArrayElementDoesNotMatch() throws JSONException {
		ArrayValueMatcher<Object> innerArrayValueMatcher = new ArrayValueMatcher<Object>(comparator, 1);
		JSONComparator innerArrayComparator = new CustomComparator(
				JSONCompareMode.LENIENT, new Customization("a[2]", innerArrayValueMatcher));
		doFailingMatchTest("a",
				new ArrayValueMatcher<Object>(innerArrayComparator, 2),  // tests inner array i.e. [12,13,14]
				"{a:[[99]]}",
				ARRAY_OF_JSONARRAYS,
				"a\\[2\\]\\[1\\]\\s*Expected:\\s*99\\s*got:\\s*13\\s*");
	}

	@Test
	public void matchesEveryElementOfJSONObjectArray() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator), "{a:[{type:row}]}", ARRAY_OF_JSONOBJECTS);
	}

	@Test
	public void failsWhenNotEveryElementOfJSONObjectArrayMatches() throws JSONException {
		doFailingMatchTest("a",
				new ArrayValueMatcher<Object>(comparator),
				"{a:[{background:white}]}",
				ARRAY_OF_JSONOBJECTS,
				"a\\[1\\]\\.background\\s*Expected:\\s*white\\s*got:\\s*grey\\s*;\\s*a\\[3\\]\\.background\\s*Expected:\\s*white\\s*got:\\s*grey\\s*");
	}

	@Test
	public void matchesEveryElementOfJSONObjectArrayWhenRangeTooLarge() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator, 0, 500), "{a:[{type:row}]}", ARRAY_OF_JSONOBJECTS);
	}

	@Test
	public void matchesElementPairsStartingFromElement1OfJSONObjectArrayWhenRangeTooLarge() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator, 1, 500), "{a:[{background:grey},{background:white}]}", ARRAY_OF_JSONOBJECTS);
	}

	@Test
	public void matchesElementPairsStartingFromElement0OfJSONObjectArrayWhenRangeTooLarge() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator), "{a:[{background:white},{background:grey}]}", ARRAY_OF_JSONOBJECTS);
	}

	@Test
	public void failsWhenAppliedToNonArray() throws JSONException {
		try {
			doTest("a", new ArrayValueMatcher<Object>(comparator), "{a:[{background:white}]}", "{a:{attr1:value1,attr2:value2}}");
		}
		catch (IllegalArgumentException e) {
			assertEquals("Exception message", "ArrayValueMatcher applied to non-array actual value", e.getMessage());
			return;
		}
		fail("Did not throw IllegalArgumentException");
	}

	/*
	 * Following tests verify the ability to match an element containing either
	 * a simple value or a JSON object against simple value or JSON object
	 * without requiring expected value to be wrapped in an array reducing
	 * slightly the syntactic load on teh test author & reader.
	 */

	@Test
	public void simpleValueMatchesSecondElementOfJSONObjectArray() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator, 3), "{a:4}", ARRAY_OF_INTEGERS);
	}

	@Test
	public void jsonObjectMatchesSecondElementOfJSONObjectArray() throws JSONException {
		doTest("a", new ArrayValueMatcher<Object>(comparator, 1), "{a:{background:grey,id:2,type:row}}", ARRAY_OF_JSONOBJECTS);
	}

	/*
	 * Following tests contain copies of code quoted in ArrayValueMatcher JavaDoc and are included to verify that the exact code documented works as expected.
	 */
	@Test
	public void verifyIdAttributeOfFirstArrayElementMatches() throws JSONException {
		JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);
		Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 0));
		JSONAssert.assertEquals("{a:[{id:1}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifyIdAttributeOfFirstArrayElementMatchesSimplifiedExpectedSyntax() throws JSONException {
		JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);
		Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 0));
		JSONAssert.assertEquals("{a:{id:1}}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifyTypeAttributeOfSecondAndThirdElementMatchesRow() throws JSONException {
		JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);
		Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 1, 2));
		JSONAssert.assertEquals("{a:[{type:row}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifyTypeAttributeOfEveryArrayElementMatchesRow() throws JSONException {
		 JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);
		 Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator));
		 JSONAssert.assertEquals("{a:[{type:row}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifyEveryArrayElementWithCustomComparator() throws JSONException {
		// get length of array we will verify
		int aLength = ((JSONArray)((JSONObject)JSONParser.parseJSON(ARRAY_OF_JSONOBJECTS)).get("a")).length();
		// create array of customizations one for each array element
		RegularExpressionValueMatcher<Object> regExValueMatcher = new RegularExpressionValueMatcher<Object>("\\d+");  // matches one or more digits
		Customization[] customizations = new Customization[aLength];
		for (int i=0; i<aLength; i++) {
			String contextPath = "a["+i+"].id";
			customizations[i] = new Customization(contextPath, regExValueMatcher);
		}
		CustomComparator regExComparator = new CustomComparator(JSONCompareMode.STRICT_ORDER, customizations);
		ArrayValueMatcher<Object> regExArrayValueMatcher = new ArrayValueMatcher<Object>(regExComparator);
		Customization regExArrayValueCustomization = new Customization("a", regExArrayValueMatcher);
		CustomComparator regExCustomArrayValueComparator = new CustomComparator(JSONCompareMode.STRICT_ORDER, new Customization[] { regExArrayValueCustomization });
		JSONAssert.assertEquals("{a:[{id:X}]}", ARRAY_OF_JSONOBJECTS, regExCustomArrayValueComparator);
	}

	@Test
	public void verifyBackgroundAttributesOfEveryArrayElementAlternateBetweenWhiteAndGrey() throws JSONException {
		 JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);
		 Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator));
		 JSONAssert.assertEquals("{a:[{background:white},{background:grey}]}", ARRAY_OF_JSONOBJECTS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifyEveryElementOfArrayIsJSONArrayOfLength3() throws JSONException {
		 JSONComparator comparator = new ArraySizeComparator(JSONCompareMode.STRICT_ORDER);
		 Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 0, 2));
		 JSONAssert.assertEquals("{a:[[3]]}", ARRAY_OF_JSONARRAYS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifySecondElementOfArrayIsJSONArrayWhoseFirstElementIs9() throws JSONException {
		 Customization innerCustomization = new Customization("a[1]", new ArrayValueMatcher<Object>(comparator, 0));
		 JSONComparator comparator = new CustomComparator(JSONCompareMode.LENIENT, innerCustomization);
		 Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 1));
		 JSONAssert.assertEquals("{a:[[9]]}", ARRAY_OF_JSONARRAYS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifySecondElementOfArrayIsJSONArrayWhoseFirstElementIs9WithSimpliedExpectedString() throws JSONException {
		 Customization innerCustomization = new Customization("a[1]", new ArrayValueMatcher<Object>(comparator, 0));
		 JSONComparator comparator = new CustomComparator(JSONCompareMode.LENIENT, innerCustomization);
		 Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 1));
		 JSONAssert.assertEquals("{a:[9]}", ARRAY_OF_JSONARRAYS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}

	@Test
	public void verifySecondElementOfArrayIsJSONArrayWhoseFirstElementIs9WithEvenMoreSimpliedExpectedString() throws JSONException {
		 Customization innerCustomization = new Customization("a[1]", new ArrayValueMatcher<Object>(comparator, 0));
		 JSONComparator comparator = new CustomComparator(JSONCompareMode.LENIENT, innerCustomization);
		 Customization customization = new Customization("a", new ArrayValueMatcher<Object>(comparator, 1));
		 JSONAssert.assertEquals("{a:9}", ARRAY_OF_JSONARRAYS, new CustomComparator(JSONCompareMode.LENIENT, customization));
	}
}


text:
require "rails_helper"

describe Widget, type: :model do
  describe "`be_versioned` matcher" do
    it { is_expected.to be_versioned }
  end

  let(:widget) { Widget.create! name: "Bob", an_integer: 1 }

  describe "`have_a_version_with` matcher", versioning: true do
    before do
      widget.update_attributes!(name: "Leonard", an_integer: 1)
      widget.update_attributes!(name: "Tom")
      widget.update_attributes!(name: "Bob")
    end

    it "is possible to do assertions on versions" do
      expect(widget).to have_a_version_with name: "Leonard", an_integer: 1
      expect(widget).to have_a_version_with an_integer: 1
      expect(widget).to have_a_version_with name: "Tom"
    end
  end

  describe "versioning option" do
    context "enabled", versioning: true do
      it "should enable versioning" do
        expect(widget.versions.size).to eq(1)
      end
    end

    context "disabled (default)" do
      it "should not enable versioning" do
        expect(widget.versions.size).to eq(0)
      end
    end
  end

  describe "Callbacks", versioning: true do
    describe :before_save do
      context ":on => :update" do
        before { widget.update_attributes!(name: "Foobar") }

        subject { widget.versions.last.reify }

        it "resets value for timestamp attrs for update so that value gets updated properly" do
          # Travel 1 second because MySQL lacks sub-second resolution
          Timecop.travel(1) do
            expect { subject.save! }.to change(subject, :updated_at)
          end
        end
      end
    end

    describe :after_create do
      let(:widget) { Widget.create!(name: "Foobar", created_at: Time.now - 1.week) }

      it "corresponding version should use the widget's `updated_at`" do
        expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
      end
    end

    describe :after_update do
      before { widget.update_attributes!(name: "Foobar", updated_at: Time.now + 1.week) }

      subject { widget.versions.last.reify }

      it { expect(subject).not_to be_live }

      it "should clear the `versions_association_name` virtual attribute" do
        subject.save!
        expect(subject).to be_live
      end

      it "corresponding version should use the widget updated_at" do
        expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
      end
    end

    describe :after_destroy do
      it "should create a version for that event" do
        expect { widget.destroy }.to change(widget.versions, :count).by(1)
      end

      it "should assign the version into the `versions_association_name`" do
        expect(widget.version).to be_nil
        widget.destroy
        expect(widget.version).not_to be_nil
        expect(widget.version).to eq(widget.versions.last)
      end
    end

    describe :after_rollback do
      let(:rolled_back_name) { "Big Moo" }

      before do
        begin
          widget.transaction do
            widget.update_attributes!(name: rolled_back_name)
            widget.update_attributes!(name: Widget::EXCLUDED_NAME)
          end
        rescue ActiveRecord::RecordInvalid
          widget.reload
          widget.name = nil
          widget.save
        end
      end

      it "does not create an event for changes that did not happen" do
        widget.versions.map(&:changeset).each do |changeset|
          expect(changeset.fetch("name", [])).to_not include(rolled_back_name)
        end
      end

      it "has not yet loaded the assocation" do
        expect(widget.versions).to_not be_loaded
      end
    end
  end

  describe "Association", versioning: true do
    describe "sort order" do
      it "should sort by the timestamp order from the `VersionConcern`" do
        expect(widget.versions.to_sql).to eq(
          widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql)
      end
    end
  end

  if defined?(ActiveRecord::IdentityMap) && ActiveRecord::IdentityMap.respond_to?(:without)
    describe "IdentityMap", versioning: true do
      it "should not clobber the IdentityMap when reifying" do
        widget.update_attributes name: "Henry", created_at: Time.now - 1.day
        widget.update_attributes name: "Harry"
        expect(ActiveRecord::IdentityMap).to receive(:without).once
        widget.versions.last.reify
      end
    end
  end

  describe "Methods" do
    describe "Instance", versioning: true do
      describe '#paper_trail_originator' do
        it { is_expected.to respond_to(:paper_trail_originator) }

        describe "return value" do
          let(:orig_name) { FFaker::Name.name }
          let(:new_name) { FFaker::Name.name }
          before { PaperTrail.whodunnit = orig_name }

          context "accessed from live model instance" do
            specify { expect(widget).to be_live }

            it "should return the originator for the model at a given state" do
              expect(widget.paper_trail_originator).to eq(orig_name)
              widget.whodunnit(new_name) { |w| w.update_attributes(name: "Elizabeth") }
              expect(widget.paper_trail_originator).to eq(new_name)
            end
          end

          context "accessed from a reified model instance" do
            before do
              widget.update_attributes(name: "Andy")
              PaperTrail.whodunnit = new_name
              widget.update_attributes(name: "Elizabeth")
            end

            context "default behavior (no `options[:dup]` option passed in)" do
              let(:reified_widget) { widget.versions[1].reify }

              it "should return the appropriate originator" do
                expect(reified_widget.paper_trail_originator).to eq(orig_name)
              end

              it "should not create a new model instance" do
                expect(reified_widget).not_to be_new_record
              end
            end

            context "creating a new instance (`options[:dup] == true`)" do
              let(:reified_widget) { widget.versions[1].reify(dup: true) }

              it "should return the appropriate originator" do
                expect(reified_widget.paper_trail_originator).to eq(orig_name)
              end

              it "should not create a new model instance" do
                expect(reified_widget).to be_new_record
              end
            end
          end
        end
      end

      describe "#originator" do
        subject { widget }

        it { is_expected.to respond_to(:originator) }

        it "should set the invoke `paper_trail_originator`" do
          allow(::ActiveSupport::Deprecation).to receive(:warn)
          is_expected.to receive(:paper_trail_originator)
          subject.originator
        end

        it "should display a deprecation warning" do
          expect(::ActiveSupport::Deprecation).to receive(:warn).
            with(/Use paper_trail_originator instead of originator/)
          subject.originator
        end
      end

      describe '#version_at' do
        it { is_expected.to respond_to(:version_at) }

        context "Timestamp argument is AFTER object has been destroyed" do
          before do
            widget.update_attribute(:name, "foobar")
            widget.destroy
          end

          it "should return `nil`" do
            expect(widget.version_at(Time.now)).to be_nil
          end
        end
      end

      describe '#whodunnit' do
        it { is_expected.to respond_to(:whodunnit) }

        context "no block given" do
          it "should raise an error" do
            expect {
              widget.whodunnit("Ben")
            }.to raise_error(ArgumentError, "expected to receive a block")
          end
        end

        context "block given" do
          let(:orig_name) { FFaker::Name.name }
          let(:new_name) { FFaker::Name.name }

          before do
            PaperTrail.whodunnit = orig_name
            expect(widget.versions.last.whodunnit).to eq(orig_name) # persist `widget`
          end

          it "should modify value of `PaperTrail.whodunnit` while executing the block" do
            widget.whodunnit(new_name) do
              expect(PaperTrail.whodunnit).to eq(new_name)
              widget.update_attributes(name: "Elizabeth")
            end
            expect(widget.versions.last.whodunnit).to eq(new_name)
          end

          context "after executing the block" do
            it "reverts value of whodunnit to previous value" do
              widget.whodunnit(new_name) { |w| w.update_attributes(name: "Elizabeth") }
              expect(PaperTrail.whodunnit).to eq(orig_name)
            end
          end

          context "error within block" do
            it "still reverts the whodunnit value to previous value" do
              expect {
                widget.whodunnit(new_name) { raise }
              }.to raise_error(RuntimeError)
              expect(PaperTrail.whodunnit).to eq(orig_name)
            end
          end
        end
      end

      describe '#touch_with_version' do
        it { is_expected.to respond_to(:touch_with_version) }

        it "creates a version" do
          count = widget.versions.size
          # Travel 1 second because MySQL lacks sub-second resolution
          Timecop.travel(1) do
            widget.touch_with_version
          end
          expect(widget.versions.size).to eq(count + 1)
        end

        it "increments the `:updated_at` timestamp" do
          time_was = widget.updated_at
          # Travel 1 second because MySQL lacks sub-second resolution
          Timecop.travel(1) do
            widget.touch_with_version
          end
          expect(widget.updated_at).to be > time_was
        end
      end
    end

    describe "Class" do
      subject { Widget }

      describe "#paper_trail_enabled_for_model?" do
        it { is_expected.to respond_to(:paper_trail_enabled_for_model?) }

        it { expect(subject.paper_trail_enabled_for_model?).to be true }
      end

      describe '#paper_trail_off!' do
        it { is_expected.to respond_to(:paper_trail_off!) }

        it "should set the `paper_trail_enabled_for_model?` to `false`" do
          expect(subject.paper_trail_enabled_for_model?).to be true
          subject.paper_trail_off!
          expect(subject.paper_trail_enabled_for_model?).to be false
        end
      end

      describe '#paper_trail_on!' do
        before { subject.paper_trail_off! }

        it { is_expected.to respond_to(:paper_trail_on!) }

        it "should set the `paper_trail_enabled_for_model?` to `true`" do
          expect(subject.paper_trail_enabled_for_model?).to be false
          subject.paper_trail_on!
          expect(subject.paper_trail_enabled_for_model?).to be true
        end
      end
    end
  end
end