/*! @author jhc_ */ #ifndef _POINTER_H #define _POINTER_H #include #include "index.h" #include "mappable.h" #include "abstractpointer.h" /*! * @brief On demand cached mapped object, acts as normal pointers * * Pointer is a key class to efficiency in our ORM implementation : * it has two main purposes : to provides a cache mechanism for mapped * objects, to ensure unicity in memory of mapped objects and to * instanciate mapped objects on demand. * * Why do we have to bother with such issues ? * * To start, Caching is just a matter of performance, every modern * software includes a such feature and not having it would severly hit * reactivity in a gui using it, even if it runs asynchronously in a * separated thread ( it would be smoother but still slow ). Concretely, * caching mapped objects means that if we want to fetch an object that we * already fetched, we don't have to walk through the database anymore. * But this introduces the requirement for the cache to know when database * has been modified by someone else on the network. * We use a watchdog for solving this ( @see DbConnection for more details. ). * * Next, Unicity. Why do we bother ourselves with unicity ? * To avoid algorithmic crazyness when trying to resolve conflicts * when the same objects instanciated twice has been modified differently * on each instance. Let's take an example : we have User u, which is * an attribute of Group g1 and g2. This means, without mapped object * unicity, that u is instanciated twice, with same data fetched from * the database. Le'ts imagine, that someone modify u name is g1 and someone * else modify u's phone and u's name in g2. How should we behave ? * That is what was meant when using the term "algorithmic crazyness". * * To solve this tedious problem, we reuse the caching system : instead * of just instanciating another object with data brought from the cache, * we simply return a reference to the current instance of this object. * * This make the cache acts at two levels : database data, mapped objects * instances. * * Finally, what is "on demand instanciation" ? It's the answer to a * big performance problem. Normally, if we basically instanciate every * mapped attributes of each mapped class, we will end by transitive relations * in instanciating all objects possible using associations between objects. * * If User u is referenced by Group g1, we have to instanciate g1. But g1 has * many users in its references, so we have to instanciates them too. And it * goes like this for a while. * * By instancianting objects only when they are explicitely needed * we completely avoid the previous problem. Concretely, it means that at least * a mapped object, even if not instanciated has a database index to know * where to fetch informations. If you try to use an object through a Pointer, * if it isn't loaded and not cached, it will be loaded implicitely, added to * the cache for further usage. This mechanism is activated when accessing the * pointer using any operator that access to object itself : * * Pointer u(Index(4,"User"); * Using indirection operators ( *u and -> ) will trigger this mechanism. * * @Warning Pointer is intended to manage heap allocated objects, and will act * as a basic garbage collector. */ template class Pointer : public AbstractPointer { private: T * m_ptr; Index m_index; public: DECLARE_EXCEPTION(NotMappable); inline Pointer() : m_ptr(NULL) { } /*! Constructs a Pointer by using an already instanciated object */ inline Pointer(T * ptr) { m_ptr = ptr; if(!m_ptr) return; m_index = m_ptr->_index(); if( AbstractPointer::sm_cache.contains(m_index) ) { if( m_ptr != AbstractPointer::sm_cache.value(m_index)) { qWarning() << "Pointer : referenced objects aren't the same"; AbstractPointer::sm_cache[m_index] = m_ptr; } } else { AbstractPointer::sm_cache.insert(m_index, m_ptr); } } inline Pointer(Index index) : m_ptr(NULL), m_index(index) { if(AbstractPointer::sm_cache.value(index) == NULL) { m_ptr = static_cast(sm_cache[index]); AbstractPointer::sm_counter[index]++; } else AbstractPointer::sm_counter.insert(index,1); qDebug() << AbstractPointer::sm_counter.value(index); } inline Pointer(const Pointer & pointer) { operator=(pointer); } inline T * ptr() const { if( ! m_index.isNull() ) return static_cast(AbstractPointer::sm_cache.value(m_index)); return m_ptr; } Index index() const { return m_index; } inline ~Pointer() { decreaseReference(); } inline bool isNull() { return !m_ptr; } inline T * operator->() { return access(); } inline T & operator*() { return *access(); } inline operator T*() { return access(); } inline const Pointer & operator=(const Pointer & pointer) { if(pointer.ptr() == NULL) { decreaseReference(); m_ptr = NULL; m_index = pointer.index(); return *this; } if(m_ptr == pointer.ptr() || m_index == pointer.ptr()->_index()) return *this; decreaseReference(); /* Since it's already a Pointer, we do not have to check for existance */ m_ptr = pointer.ptr(); m_index = pointer.index(); sm_counter[m_ptr->_index()]++; return *this; } inline const Pointer & operator=(T * ptr) { decreaseReference(); if( ptr == NULL ) { m_ptr = ptr; m_index = Index(); } else { m_ptr = ptr; m_index = ptr->_index; } return *this; } void decreaseReference() { if(!m_ptr || m_index.isNull()) return; int remaining = --AbstractPointer::sm_counter[m_index]; qDebug() << remaining; if(remaining <= 0) { //delete m_ptr; //AbstractPointer::sm_cache.remove(m_index); qDebug() << "Pointer : no more reference to" << m_index.toString(); } else qDebug() << "Pointer : remaining" << sm_counter[m_index] << "references to" << m_index.toString(); } T * access() { /* if we don't make reference to an object, do nothing */ if(m_index.isNull()) { qDebug() << "Pointer : refers to nothing, can't access"; return NULL; } /* if m_ptr is NULL, check for pointer state in hash*/ if(m_ptr == NULL) { /* if corresponding value in hash is still empty, fetch it from db * this is a "cache miss" */ if( AbstractPointer::sm_cache.value(m_index) == NULL ) { m_ptr = new T(); m_ptr->setId(m_index.id()); sm_hitCounter[m_ptr->_index()] = 0; qDebug() << "Pointer : cache miss, fetching" << m_ptr->_index().toString() << "from database"; m_ptr->_fetch(); AbstractPointer::sm_cache[m_index] = m_ptr; } else /* "cache hit" */ m_ptr = static_cast(AbstractPointer::sm_cache.value(m_index)); } sm_hitCounter[m_ptr->_index()]++; return m_ptr; } void setIndex(const Index & index) { decreaseReference(); m_ptr = NULL; m_index = index; } }; #endif