////////////////////////////////////////////////////////////////////////////////
// The Loki Library
// Copyright (c) 2006 by Guillaume Chatelet
//
// Code covered by the MIT License
//
// Permission to use, copy, modify, distribute and sell this software for any 
// purpose is hereby granted without fee, provided that the above copyright 
// notice appear in all copies and that both that copyright notice and this 
// permission notice appear in supporting documentation.
//
// The authors make no representations about the suitability of this software
// for any purpose. It is provided "as is" without express or implied warranty.
//
// This code DOES NOT accompany the book:
// Alexandrescu, Andrei. "Modern C++ Design: Generic Programming and Design 
//     Patterns Applied". Copyright (c) 2001. Addison-Wesley.
//
////////////////////////////////////////////////////////////////////////////////
#ifndef LOKI_CACHEDFACTORY_INC_
#define LOKI_CACHEDFACTORY_INC_

// $Id: CachedFactory.h 950 2009-01-26 19:45:54Z syntheticpp $

#include <functional>
#include <algorithm>
#include <iostream>
#include <vector>
#include <iterator>
#include <map>
#include <cassert>
#include <loki/Key.h>

#ifdef DO_EXTRA_LOKI_TESTS
	#define D( x ) x
#else
	#define D( x ) ;
#endif

#if defined(_MSC_VER)  || defined(__CYGWIN__)
#include <time.h>
#endif

/**
 * \defgroup	FactoriesGroup Factories
 * \defgroup	CachedFactoryGroup Cached Factory
 * \ingroup		FactoriesGroup
 * \brief		CachedFactory provides an extension of a Factory with caching
 * support.
 * 
 * Once used objects are returned to the CachedFactory that manages its
 * destruction.
 * If your code uses lots of "long to construct/destruct objects" using the
 * CachedFactory will surely speedup the execution.
 */
namespace Loki
{
/**
 * \defgroup	EncapsulationPolicyCachedFactoryGroup	Encapsulation policies
 * \ingroup	CachedFactoryGroup
 * \brief	Defines how the object is returned to the client
 */
	/**
	 * \class	SimplePointer
	 * \ingroup	EncapsulationPolicyCachedFactoryGroup
	 * \brief	No encaspulation : returns the pointer
	 * 
	 * This implementation does not make any encapsulation.
	 * It simply returns the object's pointer.
	 */
     template<class AbstractProduct>     
     class SimplePointer
     {
     protected:
           typedef AbstractProduct* ProductReturn;
           ProductReturn encapsulate(AbstractProduct* pProduct)
           {
                return pProduct;
           }
           
           AbstractProduct* release(ProductReturn &pProduct)
           {
                AbstractProduct* pPointer(pProduct);
                pProduct=NULL;
                return pPointer;
           }
           const char* name(){return "pointer";}
     };

/**
 * \defgroup	CreationPolicyCachedFactoryGroup		Creation policies
 * \ingroup		CachedFactoryGroup
 * \brief		Defines a way to limit the creation operation.
 * 
 * For instance one may want to be alerted (Exception) when
 * - Cache has created a more than X object within the last x seconds
 * - Cache creation rate has increased dramatically
 * .
 * which may result from bad caching strategy, or critical overload
 */
	/**
	 * \class	NeverCreate
	 * \ingroup	CreationPolicyCachedFactoryGroup
	 * \brief	Never allows creation. Testing purposes only.
	 * 
	 * Using this policy will throw an exception.
	 */
     class NeverCreate
     {
     protected:
            struct Exception : public std::exception
            {
                const char* what() const throw() { return "NeverFetch Policy : No Fetching allowed"; }
            };
            
            bool canCreate()
            {
                throw Exception();
            }
            
            void onCreate(){}
            void onDestroy(){}
            const char* name(){return "never";}
     };
     
     /**
      * \class		AlwaysCreate
      * \ingroup	CreationPolicyCachedFactoryGroup
      * \brief		Always allows creation.
      * 
      * Doesn't limit the creation in any way
      */
     class AlwaysCreate
     {
     protected:
            bool canCreate()
            {
                return true;
            }

            void onCreate(){}
            void onDestroy(){}
            const char* name(){return "always";}
     };


    /**
     * \class	RateLimitedCreation
     * \ingroup	CreationPolicyCachedFactoryGroup
     * \brief	Limit in rate.
     * 
     * This implementation will prevent from Creating more than maxCreation objects
     * within byTime ms by throwing an exception.
     * Could be usefull to detect prevent loads (http connection for instance).
     * Use the setRate method to set the rate parameters.
     * default is 10 objects in a second.
     */
     // !! CAUTION !!
     // The std::clock() function is not quite precise
     // under linux this policy might not work.
     // TODO : get a better implementation (platform dependant)
     class RateLimitedCreation
     {
     private:
            typedef std::vector< clock_t > Vector;
            Vector m_vTimes;
            unsigned maxCreation;
            clock_t timeValidity;
            clock_t lastUpdate;
          
            void cleanVector()
            {
            	using namespace std;
				clock_t currentTime = clock();
                D( cout << "currentTime = " << currentTime<< endl; )
                D( cout << "currentTime - lastUpdate = " << currentTime - lastUpdate<< endl; )
                if(currentTime - lastUpdate > timeValidity)
                {
                    m_vTimes.clear();
                    D( cout << " is less than time validity " << timeValidity; )
                    D( cout << " so clearing vector" << endl; )
                }
                else
                {
                    D( cout << "Cleaning time less than " << currentTime - timeValidity << endl; )
                    D( displayVector(); )
                    Vector::iterator newEnd = remove_if(m_vTimes.begin(), m_vTimes.end(), bind2nd(less<clock_t>(), currentTime - timeValidity));
                    // this rearrangement might be costly, consider optimization
                    // by calling cleanVector in less used onCreate function
                    // ... although it may not be correct
                    m_vTimes.erase(newEnd, m_vTimes.end());
                    D( displayVector(); )
                }
                lastUpdate = currentTime;
            }
#ifdef DO_EXTRA_LOKI_TESTS        
            void displayVector()
            {
                std::cout << "Vector : ";
                copy(m_vTimes.begin(), m_vTimes.end(), std::ostream_iterator<clock_t>(std::cout, " "));
                std::cout << std::endl;
            }
#endif
     protected:
            RateLimitedCreation() : maxCreation(10), timeValidity(CLOCKS_PER_SEC), lastUpdate(clock())
            {}
            
            struct Exception : public std::exception
            {
                const char* what() const throw() { return "RateLimitedCreation Policy : Exceeded the authorized creation rate"; }
            };
            
            bool canCreate()
            {
                cleanVector();
                if(m_vTimes.size()>maxCreation)
                    throw Exception();
                else
                    return true;
            }

            void onCreate()
            {
				m_vTimes.push_back(clock());
            }
            
            void onDestroy()
            {
            }
            const char* name(){return "rate limited";}
     public:
            // set the creation rate
            // No more than maxCreation within byTime milliseconds
            void setRate(unsigned maxCreation, unsigned byTime)
            {
                assert(byTime>0);
                this->maxCreation = maxCreation;
                this->timeValidity = static_cast<clock_t>(byTime * CLOCKS_PER_SEC / 1000);
                D( std::cout << "Setting no more than "<< maxCreation <<" creation within " << this->timeValidity <<" ms"<< std::endl; )
            }
     };
     
    /**
     * \class	AmountLimitedCreation
     * \ingroup	CreationPolicyCachedFactoryGroup
     * \brief	Limit by number of objects
     * 
     * This implementation will prevent from Creating more than maxCreation objects
     * within byTime ms by calling eviction policy.
     * Use the setRate method to set the rate parameters.
     * default is 10 objects.
     */
     class AmountLimitedCreation
     {
     private:
            unsigned maxCreation;
            unsigned created;
            
     protected:
            AmountLimitedCreation() : maxCreation(10), created(0)
            {}
            
            bool canCreate()
            {
            	return !(created>=maxCreation);
            }

            void onCreate()
            {
                ++created;
            }
            
            void onDestroy()
            {
                --created;
            }
            const char* name(){return "amount limited";}
     public:
            // set the creation max amount
            void setMaxCreation(unsigned maxCreation)
            {
                assert(maxCreation>0);
                this->maxCreation = maxCreation;
                D( std::cout << "Setting no more than " << maxCreation <<" creation" << std::endl; )
            }
     };
     
/**
 * \defgroup	EvictionPolicyCachedFactoryGroup		Eviction policies
 * \ingroup	CachedFactoryGroup
 * \brief	Gathers informations about the stored objects and choose a
 * candidate for eviction.
 */

    class EvictionException : public std::exception
    {
    public:
        const char* what() const throw() { return "Eviction Policy : trying to make room but no objects are available"; }
    };

    // The following class is intented to provide helpers to sort
    // the container that will hold an eviction score
    template
    <
    	typename ST, // Score type
    	typename DT // Data type
    >
    class EvictionHelper
    {
    protected:
        typedef typename std::map< DT, ST >			HitMap;
        typedef typename HitMap::iterator			HitMapItr;
    private:
        typedef std::pair< ST, DT >					SwappedPair;
        typedef std::multimap< ST, DT >				SwappedHitMap;
    	typedef	typename SwappedHitMap::iterator	SwappedHitMapItr;
    protected:
        HitMap										m_mHitCount;
    	
    	// This function sorts the map according to the score
    	// and returns the lower bound of the sorted container
    	DT&	getLowerBound(){
    		assert(!m_mHitCount.empty());
    		// inserting the swapped pair into a multimap
    		SwappedHitMap copyMap;
    		for(HitMapItr itr = m_mHitCount.begin(); itr != m_mHitCount.end(); ++itr)
    			copyMap.insert(SwappedPair((*itr).second, (*itr).first));
    		if((*copyMap.rbegin()).first == 0) // the higher score is 0 ...
    		  throw EvictionException(); // there is no key evict
    		return (*copyMap.begin()).second;
    	}
    };
    
	/**
	 * \class	EvictLRU
	 * \ingroup	EvictionPolicyCachedFactoryGroup
	 * \brief	Evicts least accessed objects first.
	 * 
	 * Implementation of the Least recent used algorithm as
	 * described in http://en.wikipedia.org/wiki/Page_replacement_algorithms .
	 * 
	 * WARNING : If an object is heavily fetched
	 * (more than ULONG_MAX = UINT_MAX = 4294967295U)
	 * it could unfortunately be removed from the cache.
	 */
    template
    <
    	typename DT, // Data Type (AbstractProduct*)
    	typename ST = unsigned // default data type to use as Score Type
    >
    class EvictLRU : public EvictionHelper< ST , DT >
    {
    private:
    	typedef EvictionHelper< ST , DT >	EH;
    protected:
        
        virtual ~EvictLRU(){}
        
    	// OnStore initialize the counter for the new key
    	// If the key already exists, the counter is reseted
    	void onCreate(const DT& key)
        {
    		EH::m_mHitCount[key] = 0;
    	}
    	
    	void onFetch(const DT&)
        {
    	}
    	
    	// onRelease increments the hit counter associated with the object
        void onRelease(const DT& key)
        {
    		++(EH::m_mHitCount[key]);
        }

    	void onDestroy(const DT& key)
    	{
            EH::m_mHitCount.erase(key);
    	}
    	
    	// this function is implemented in Cache and redirected
    	// to the Storage Policy
    	virtual void remove(DT const key)=0;

    	// LRU Eviction policy
    	void evict()
    	{
    		remove(EH::getLowerBound());
    	}
        const char* name(){return "LRU";}
    };
    
	/**
	 * \class	EvictAging
	 * \ingroup	EvictionPolicyCachedFactoryGroup
	 * \brief	LRU aware of the time span of use
	 * 
	 * Implementation of the Aging algorithm as
	 * described in http://en.wikipedia.org/wiki/Page_replacement_algorithms .
	 * 
	 * This method is much more costly than evict LRU so
	 * if you need extreme performance consider switching to EvictLRU
	 */
    template
    <
    	typename DT, // Data Type (AbstractProduct*)
    	typename ST = unsigned // default data type to use as Score Type
    >
    class EvictAging : public EvictionHelper< ST, DT >
    {
    private:
        EvictAging(const EvictAging&);
        EvictAging& operator=(const EvictAging&);
    	typedef EvictionHelper< ST, DT >		       		EH;
    	typedef typename EH::HitMap						HitMap;
    	typedef typename EH::HitMapItr					HitMapItr;
    	
    	// update the counter
		template<class T> struct updateCounter : public std::unary_function<T, void>
		{
			updateCounter(const DT& key): key_(key){}
			void operator()(T x)
			{
				x.second = (x.first == key_ ? (x.second >> 1) | ( 1 << ((sizeof(ST)-1)*8) ) : x.second >> 1);
    			D( std::cout <<  x.second << std::endl; )
			}
			const DT &key_;
            updateCounter(const updateCounter& rhs) : key_(rhs.key_){}
        private:
            updateCounter& operator=(const updateCounter& rhs);
		};
    protected:
         EvictAging(){}
         virtual ~EvictAging(){}
         
    	// OnStore initialize the counter for the new key
    	// If the key already exists, the counter is reseted
    	void onCreate(const DT& key){
    		EH::m_mHitCount[key] = 0;
    	}
    	
    	void onFetch(const DT&){}
    	
    	// onRelease increments the hit counter associated with the object
    	// Updating every counters by iterating over the map
    	// If the key is the key of the fetched object :
    	//  the counter is shifted to the right and it's MSB is set to 1
    	// else
    	//  the counter is shifted to the left
        void onRelease(const DT& key)
        {
        	std::for_each(EH::m_mHitCount.begin(), EH::m_mHitCount.end(), updateCounter< typename HitMap::value_type >(key));
        }
        
        void onDestroy(const DT& key)
        {
            EH::m_mHitCount.erase(key);
        }

    	// this function is implemented in Cache and redirected
    	// to the Storage Policy
    	virtual void remove(DT const key)=0;

    	// LRU with Aging Eviction policy
    	void evict()
    	{
    		remove(EH::getLowerBound());
    	}
        const char* name(){return "LRU with aging";}
    };
    
	/**
	 * \class	EvictRandom
	 * \ingroup	EvictionPolicyCachedFactoryGroup
	 * \brief	Evicts a random object
	 * 
	 * Implementation of the Random algorithm as
	 * described in http://en.wikipedia.org/wiki/Page_replacement_algorithms .
	 */
    template
    <
    	typename DT, // Data Type (AbstractProduct*)
    	typename ST = void // Score Type not used by this policy
    >
    class EvictRandom
    {
    private:
    	std::vector< DT >	m_vKeys;
    	typedef typename std::vector< DT >::size_type	size_type;
    	typedef typename std::vector< DT >::iterator		iterator;

    protected:
    
     	virtual ~EvictRandom(){}
     	
    	void onCreate(const DT&){
    	}
    	
    	void onFetch(const DT& ){
    	}

    	void onRelease(const DT& key){
            m_vKeys.push_back(key);
    	}
    	
    	void onDestroy(const DT& key){
    		using namespace std;
            m_vKeys.erase(remove_if(m_vKeys.begin(), m_vKeys.end(), bind2nd(equal_to< DT >(), key)), m_vKeys.end());
    	}
    	
    	// Implemented in Cache and redirected to the Storage Policy
    	virtual void remove(DT const key)=0;

    	// Random Eviction policy
    	void evict()
    	{
    		if(m_vKeys.empty())
    		    throw EvictionException();
    		size_type random = static_cast<size_type>((m_vKeys.size()*rand())/(static_cast<size_type>(RAND_MAX) + 1));
    		remove(*(m_vKeys.begin()+random));
    	}
        const char* name(){return "random";}
    };

/**
 * \defgroup	StatisticPolicyCachedFactoryGroup		Statistic policies
 * \ingroup	CachedFactoryGroup
 * \brief	Gathers information about the cache.
 * 
 * For debugging purpose this policy proposes to gather informations
 * about the cache. This could be useful to determine whether the cache is
 * mandatory or if the policies are well suited to the application.
 */
	/**
	 * \class	NoStatisticPolicy
	 * \ingroup	StatisticPolicyCachedFactoryGroup
	 * \brief	Do nothing
	 *
	 * Should be used in release code for better performances  
	 */
    class NoStatisticPolicy
    {
    protected:
        void onDebug(){}
        void onFetch(){}
        void onRelease(){}
        void onCreate(){}
        void onDestroy(){}
        const char* name(){return "no";}
    };
    
	/**
	 * \class	SimpleStatisticPolicy
	 * \ingroup	StatisticPolicyCachedFactoryGroup
	 * \brief	Simple statistics
	 *
	 * Provides the following informations about the cache :
	 * 		- Created objects
	 * 		- Fetched objects
	 * 		- Destroyed objects
	 * 		- Cache hit
	 * 		- Cache miss
	 * 		- Currently allocated
	 * 		- Currently out
	 * 		- Cache overall efficiency
	 */
    class SimpleStatisticPolicy
    {
    private:
        unsigned allocated, created, hit, out, fetched;
    protected:
        SimpleStatisticPolicy() : allocated(0), created(0), hit(0), out(0), fetched(0)
        {
        }
        
        void onDebug()
        {
        	using namespace std;
            cout << "############################" << endl;
            cout << "## About this cache " << this << endl;
            cout << "## + Created objects     : " << created << endl;
            cout << "## + Fetched objects     : " << fetched << endl;
            cout << "## + Destroyed objects   : " << created - allocated << endl;
            cout << "## + Cache hit           : " << hit << endl;
            cout << "## + Cache miss          : " << fetched - hit << endl;
            cout << "## + Currently allocated : " << allocated << endl;
            cout << "## + Currently out       : " << out << endl;
            cout << "############################" << endl;
            if(fetched!=0){
                cout << "## Overall efficiency " << 100*double(hit)/fetched <<"%"<< endl;
                cout << "############################" << endl;
            }
            cout << endl;
        }
        
        void onFetch()
        {
            ++fetched;
            ++out;
            ++hit;
        }
        void onRelease()
        {
            --out;
        }
        void onCreate()
        {
            ++created;
            ++allocated;
            --hit;
        }
        void onDestroy()
        {
            --allocated;
        }

        const char* name(){return "simple";}
    public:
        unsigned getCreated(){return created;}
        unsigned getFetched(){return fetched;}
        unsigned getHit(){return hit;}
        unsigned getMissed(){return fetched - hit;}
        unsigned getAllocated(){return allocated;}
        unsigned getOut(){return out;}
        unsigned getDestroyed(){return created-allocated;}
    }; 
    
    ///////////////////////////////////////////////////////////////////////////
    // Cache Factory definition
    ///////////////////////////////////////////////////////////////////////////
    class CacheException : public std::exception
    {
    public:
        const char* what() const throw() { return "Internal Cache Error"; }
    };
	
	/**
	 * \class		CachedFactory
	 * \ingroup		CachedFactoryGroup
	 * \brief		Factory with caching support
	 * 
     * This class acts as a Factory (it creates objects)
     * but also keeps the already created objects to prevent
     * long constructions time.
	 * 
	 * Note this implementation do not retain ownership.
	 */
	 template
     <
        class AbstractProduct,
        typename IdentifierType,
        typename CreatorParmTList = NullType,
        template<class> class EncapsulationPolicy = SimplePointer,
        class CreationPolicy = AlwaysCreate,
        template <typename , typename> class EvictionPolicy = EvictRandom,
        class StatisticPolicy = NoStatisticPolicy,
        template<typename, class> class FactoryErrorPolicy = DefaultFactoryError,
        class ObjVector = std::vector<AbstractProduct*>
     >
	 class CachedFactory : 
            protected EncapsulationPolicy<AbstractProduct>,
            public CreationPolicy, public StatisticPolicy, EvictionPolicy< AbstractProduct * , unsigned >
	 {
     private:
        typedef Factory< AbstractProduct, IdentifierType, CreatorParmTList, FactoryErrorPolicy> MyFactory;
        typedef FactoryImpl< AbstractProduct, IdentifierType, CreatorParmTList > Impl;
        typedef Functor< AbstractProduct* , CreatorParmTList > ProductCreator;
        typedef EncapsulationPolicy<AbstractProduct> NP;
        typedef CreationPolicy  CP;
        typedef StatisticPolicy SP;
        typedef EvictionPolicy< AbstractProduct* , unsigned > EP;
                
        typedef typename Impl::Parm1 Parm1;
        typedef typename Impl::Parm2 Parm2;
        typedef typename Impl::Parm3 Parm3;
        typedef typename Impl::Parm4 Parm4;
        typedef typename Impl::Parm5 Parm5;
        typedef typename Impl::Parm6 Parm6;
        typedef typename Impl::Parm7 Parm7;
        typedef typename Impl::Parm8 Parm8;
        typedef typename Impl::Parm9 Parm9;
        typedef typename Impl::Parm10 Parm10;
        typedef typename Impl::Parm11 Parm11;
        typedef typename Impl::Parm12 Parm12;
        typedef typename Impl::Parm13 Parm13;
        typedef typename Impl::Parm14 Parm14;
        typedef typename Impl::Parm15 Parm15;
        
     public:
        typedef typename NP::ProductReturn ProductReturn;
     private:
        typedef Key< Impl, IdentifierType > MyKey;
        typedef std::map< MyKey, ObjVector >  KeyToObjVectorMap;
        typedef std::map< AbstractProduct*, MyKey >  FetchedObjToKeyMap;
        
        MyFactory			factory;
        KeyToObjVectorMap   fromKeyToObjVector;
        FetchedObjToKeyMap  providedObjects;
        unsigned            outObjects;

        ObjVector& getContainerFromKey(MyKey key){
            return fromKeyToObjVector[key];
        }

        AbstractProduct* const getPointerToObjectInContainer(ObjVector &entry)
        {
            if(entry.empty()) // No object available
            {   // the object will be created in the calling function.
                // It has to be created in the calling function because of
                // the variable number of parameters for CreateObject(...) method
                return NULL;
            }
            else
            {   // returning the found object
				AbstractProduct* pObject(entry.back());
				assert(pObject!=NULL);
				entry.pop_back();
                return pObject;
            }
        }
        
        bool shouldCreateObject(AbstractProduct * const pProduct){
            if(pProduct!=NULL) // object already exists
                return false;
            if(CP::canCreate()==false) // Are we allowed to Create ?
                EP::evict(); // calling Eviction Policy to clean up
            return true;
        }
        
        void ReleaseObjectFromContainer(ObjVector &entry, AbstractProduct * const object)
        {
            entry.push_back(object);
        }
        
        void onFetch(AbstractProduct * const pProduct)
        {
            SP::onFetch();
            EP::onFetch(pProduct);
            ++outObjects;
        }
        
        void onRelease(AbstractProduct * const pProduct)
        {
            SP::onRelease();
            EP::onRelease(pProduct);
            --outObjects;
        }
        
        void onCreate(AbstractProduct * const pProduct)
        {
            CP::onCreate();
            SP::onCreate();
            EP::onCreate(pProduct);
        }
        
        void onDestroy(AbstractProduct * const pProduct)
        {
            CP::onDestroy();
            SP::onDestroy();
            EP::onDestroy(pProduct);
        }
		
		// delete the object
		template<class T> struct deleteObject : public std::unary_function<T, void>
		{
			void operator()(T x){ delete x; }
		};

		// delete the objects in the vector
		template<class T> struct deleteVectorObjects : public std::unary_function<T, void>
		{
			void operator()(T x){
				ObjVector &vec(x.second);
				std::for_each(vec.begin(), vec.end(), deleteObject< typename ObjVector::value_type>());
			}
		};

		// delete the keys of the map
		template<class T> struct deleteMapKeys : public std::unary_function<T, void>
		{
			void operator()(T x){ delete x.first; }
		};            

     protected:
        virtual void remove(AbstractProduct * const pProduct)
        {
            typename FetchedObjToKeyMap::iterator fetchedItr = providedObjects.find(pProduct);
            if(fetchedItr!=providedObjects.end()) // object is unreleased.
                throw CacheException();
            bool productRemoved = false;
            typename KeyToObjVectorMap::iterator objVectorItr;
            typename ObjVector::iterator objItr;
            for(objVectorItr=fromKeyToObjVector.begin();objVectorItr!=fromKeyToObjVector.end();++objVectorItr)
            {
                ObjVector &v((*objVectorItr).second);
                objItr = remove_if(v.begin(), v.end(), std::bind2nd(std::equal_to<AbstractProduct*>(), pProduct));
                if(objItr != v.end()) // we found the vector containing pProduct and removed it
                {
                    onDestroy(pProduct); // warning policies we are about to destroy an object
                    v.erase(objItr, v.end()); // real removing
                    productRemoved = true;
                    break;
                }
            }
            if(productRemoved==false)
                throw CacheException(); // the product is not in the cache ?!
            delete pProduct; // deleting it
        }

     public:
        CachedFactory() : factory(), fromKeyToObjVector(), providedObjects(), outObjects(0)
        {
        }

        ~CachedFactory()
        {
        	using namespace std;
            // debug information
            SP::onDebug();
            // cleaning the Cache
            for_each(fromKeyToObjVector.begin(), fromKeyToObjVector.end(),
            	deleteVectorObjects< typename KeyToObjVectorMap::value_type >()
            );
            if(!providedObjects.empty())
            {
				// The factory is responsible for the creation and destruction of objects.
				// If objects are out during the destruction of the Factory : deleting anyway.
				// This might not be a good idea. But throwing an exception in a destructor is
				// considered as a bad pratice and asserting might be too much.
				// What to do ? Leaking memory or corrupting in use pointers ? hmm...
                D( cout << "====>>  Cache destructor : deleting "<< providedObjects.size()<<" in use objects  <<====" << endl << endl; )
                for_each(providedObjects.begin(), providedObjects.end(),
	                deleteMapKeys< typename FetchedObjToKeyMap::value_type >()
                );
            }
        }
        
        ///////////////////////////////////
        // Acts as the proxy pattern and //
        // forwards factory methods      //
        ///////////////////////////////////
        
        bool Register(const IdentifierType& id, ProductCreator creator)
        {
            return factory.Register(id, creator);
        }
        
        template <class PtrObj, typename CreaFn>
        bool Register(const IdentifierType& id, const PtrObj& p, CreaFn fn)
        {
            return factory.Register(id, p, fn);
        }
        
        bool Unregister(const IdentifierType& id)
        {
            return factory.Unregister(id);
        }

        /// Return the registered ID in this Factory
        std::vector<IdentifierType>& RegisteredIds()
        {
            return factory.RegisteredIds();
        }

        ProductReturn CreateObject(const IdentifierType& id)
        {
            MyKey key(id);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }
        
        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1)
        {
            MyKey key(id,p1);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2)
        {
            MyKey key(id,p1,p2);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3)
        {
            MyKey key(id,p1,p2,p3);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3, Parm4 p4)
        {
            MyKey key(id,p1,p2,p3,p4);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3, Parm4 p4, Parm5 p5)
        {
            MyKey key(id,p1,p2,p3,p4,p5);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3, Parm4 p4, Parm5 p5,
				    Parm6 p6)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3, Parm4 p4, Parm5 p5,
				    Parm6 p6, Parm7 p7 )
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3, Parm4 p4, Parm5 p5,
				    Parm6 p6, Parm7 p7, Parm8 p8)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3, Parm4 p4, Parm5 p5,
				    Parm6 p6, Parm7 p7, Parm8 p8, Parm9 p9)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8,p9);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8,key.p9);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }
        
        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1 p1, Parm2 p2, Parm3 p3, Parm4 p4, Parm5 p5,
				    Parm6 p6, Parm7 p7, Parm8 p8, Parm9 p9,Parm10 p10)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8,key.p9,key.p10);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1  p1, Parm2 p2, Parm3 p3, Parm4 p4, Parm5  p5,
				    Parm6  p6, Parm7 p7, Parm8 p8, Parm9 p9, Parm10 p10,
				    Parm11 p11)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8,key.p9,key.p10,key.p11);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1  p1,  Parm2  p2, Parm3 p3, Parm4 p4, Parm5  p5,
				    Parm6  p6,  Parm7  p7, Parm8 p8, Parm9 p9, Parm10 p10,
				    Parm11 p11, Parm12 p12)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8,key.p9,key.p10,key.p11,key.p12);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1  p1,  Parm2  p2,  Parm3  p3, Parm4 p4, Parm5  p5,
				    Parm6  p6,  Parm7  p7,  Parm8  p8, Parm9 p9, Parm10 p10,
				    Parm11 p11, Parm12 p12, Parm13 p13)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8,key.p9,key.p10,key.p11,key.p12
                ,key.p13);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1  p1,  Parm2  p2,  Parm3  p3,  Parm4  p4, Parm5  p5,
				    Parm6  p6,  Parm7  p7,  Parm8  p8,  Parm9  p9, Parm10 p10,
				    Parm11 p11, Parm12 p12, Parm13 p13, Parm14 p14)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8,key.p9,key.p10,key.p11,key.p12
                ,key.p13,key.p14);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

        ProductReturn CreateObject(const IdentifierType& id,
				    Parm1  p1,  Parm2  p2,  Parm3  p3,  Parm4  p4,  Parm5  p5,
				    Parm6  p6,  Parm7  p7,  Parm8  p8,  Parm9  p9,  Parm10 p10,
				    Parm11 p11, Parm12 p12, Parm13 p13, Parm14 p14, Parm15 p15)
        {
            MyKey key(id,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15);
            AbstractProduct *pProduct(getPointerToObjectInContainer(getContainerFromKey(key)));
            if(shouldCreateObject(pProduct))
            {
                pProduct = factory.CreateObject(key.id,key.p1,key.p2,key.p3
                ,key.p4,key.p5,key.p6,key.p7,key.p8,key.p9,key.p10,key.p11,key.p12
                ,key.p13,key.p14,key.p15);
                onCreate(pProduct);
            }
            onFetch(pProduct);
            providedObjects[pProduct] = key;
            return NP::encapsulate(pProduct);
        }

		/// Use this function to release the object
		/**
		 * if execution brakes in this function then you tried
		 * to release an object that wasn't provided by this Cache
		 * ... which is bad :-)
		 */
        void ReleaseObject(ProductReturn &object)
        {
            AbstractProduct* pProduct(NP::release(object));
            typename FetchedObjToKeyMap::iterator itr = providedObjects.find(pProduct);
            if(itr == providedObjects.end())
                throw CacheException();
            onRelease(pProduct);
            ReleaseObjectFromContainer(getContainerFromKey((*itr).second), pProduct);
            providedObjects.erase(itr);
        }
        
        /// display the cache configuration
        void displayCacheType()
        {
        	using namespace std;
            cout << "############################" << endl;
            cout << "## Cache configuration" << endl;
            cout << "## + Encapsulation " << NP::name() << endl;
            cout << "## + Creating      " << CP::name() << endl;
            cout << "## + Eviction      " << EP::name() << endl;
            cout << "## + Statistics    " << SP::name() << endl;
            cout << "############################" << endl;
        }
     };
} // namespace Loki

#endif // end file guardian

