/*

    ========== licence begin  GPL
    Copyright (c) 2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end

*/
#ifndef __OMS_OIDHASH_HPP
#define __OMS_OIDHASH_HPP

#include "Oms/OMS_ObjectContainerDef.hpp"
#include "ggg00.h"

/// Minimal resp. default resp. maximal possible size of the (adaptable) hash array 
/// in the class OMS_OidHash
#define MINIMAL_OMS_HASH_SIZE 8192
#define DEFAULT_OMS_HASH_SIZE 131072
#define MAXIMAL_OMS_HASH_SIZE 2147483648

class OMS_DumpInterface;

/// Implements access structure for objects in the local context cache
class OMS_OidHash {
  friend class OmsHandle;
  friend class OmsObjByClsIterBase;
  /*! Current number of buckets in the hash-array. This number can be changed dynamically 
  ** (see OMS_OidHash::HashResize). */
  tsp00_Int4             m_headentries;
  /*! Bit-mask to filter the relevant bit positions of the hash value depending on the 
  ** size of the hash array 
  ** \since PTS 1118855 */
  tsp00_Int4             m_mask;      
  /*! Number of entries in the cache 
  ** \since PTS 1118855 */
  tsp00_Int4             m_count;
  /*! Maximal number of entries in the cache since the last reset of the cache 
  ** \since PTS 1118855 */
  tsp00_Int4             m_maxCount;
  /*! Maximal length of a hash chain for this instance so far */
  tsp00_Int4             m_maxLen;
  /*! Backward-pointer to the context, in which the instance was created */
  OMS_Context*       m_context;
  /*! Hash array with pointers to the object container. The components of the OID are
  ** used to compute the hash value. */
  OmsObjectContainerPtr* m_head;
  /*! Pointer to the bucket in the hash array which corresponds to the hash value of the 
  ** current object. This information can be used to prevent several computations of the 
  ** hash value for the same OID. */
  OmsObjectContainerPtr* m_headcurr;

public :
  OMS_OidHash ();
  ~OMS_OidHash ();
  /// Removes all objects out of the hash and out of the context cache.
  void                         Clear();
  tgg00_BasisError             ClearForOpenVersion();
  /// Removes all objects of dropped containers out of the hash and out of the context cache.
  void                         Clean();
  /// Creates an empty hash with the given size for the context s.
  void                         Create (OMS_Context* s, const tsp00_Int4 sz = DEFAULT_OMS_HASH_SIZE);
  void                         Dump(OMS_DumpInterface& dumpObj) const;
  /// Returns the current number of hash buckets.  
  inline tsp00_Int4            GetHashSize() const;
  /// Removes the object with the given OID from the hash.
  bool                         HashDelete     (const OmsObjectId& key, bool updateKeyCache);
  /// Searches the hash for the object identified by the given OID.
  inline OmsObjectContainerPtr HashFind       (const OmsObjectId* k, bool ignoreGeneration=false);
  /// Frees the memory allocated by the hash array.
  inline void                  HashFree       ();
  /// Allocates the memory for an hash array, initializes the array and all other members of the class
  inline void                  HashInit       (tsp00_Int4 sz);
  /// Inserts an object into the hash. 
  inline void                  HashInsert     (OmsObjectContainer* h);
  /// Inserts an object into the bucket to which the member OMS_OidHash::m_headcurr is currently pointing.
  inline void                  HashSlotInsert (OmsObjectContainer* h);
  /// Removes all objects which are not locked and which do not have before-images out of the hash and out of the cache.
  void                         RemoveUnlockedObjFromCache();
  /// Reactive objects which were replaced by the now rollbacked object version.
  inline OmsObjectContainerPtr ReactivateReplacedVersion(OmsObjectContainer* p);
  /// Resets the hash array.
  void                         SetEmpty       (bool adaptOidHash = true);

  /// /name Methods for the adaptation of the hash.
  //@{
  /// Increases number of entries in the hash and if necessary adapts the hash size.
  inline void                  DecCount       ();                          
  /// Resets the number of entries to zero and if necessary adapts the hash size.
  inline void                  IncCount       ();                          
  /// Decreases number of entries in the hash.
  inline void                  ResetCount     (bool adaptOidHash = true);  
  /// Changes the size of the hash array and if requested rehashes the current entries
  void                         HashResize     (int newHeadEntries, bool rehash = true); // PTS 1118855
  //@}

  /// /name Check Routines.
  //@{
  /// Checks for loops in the current hash chain.
  int                          CheckChain     (OmsObjectContainerPtr curr);
  /// Checks for loops in any hash chain.
  void                         HashCheck      ();
  //@}

  class OidIter {
    friend class OMS_OidHash;
    OMS_OidHash*         m_hash;
    long                   m_headIndex;
    int                    m_entries;
    OmsObjectContainerPtr  m_curr;
  public :
    inline OidIter(OMS_OidHash*);
    inline OidIter(const OidIter&);
    inline ~OidIter();
    inline void operator++();
    inline void operator++(int);
    inline operator bool() const;
    inline OmsObjectContainerPtr operator()() const;
  };
  friend class OMS_OidHash::OidIter;
  inline OidIter               First ();

private :
  /*! Computation of the hash bucket for a given OID. The current hash bucket is stored
  ** in the variable m_headcurr, so that later accesses to the same OID can be executed
  ** without the recomputation of the hash value. 
  ** \attention The hash-function has to ensure, that objects, which are not created
  ** in a version, and which have the same pno and pagePos, have the same hash value 
  ** independend of the value of the generation.
  */
  OmsObjectContainerPtr* HeadPtr (const OmsObjectId& key) {
    tsp00_Uint4 idx = key.omsHashValue() & m_mask;
    m_headcurr = &m_head[idx];
    return m_headcurr;
  }

  // ChangeMaxHashChainLen is implemented as a separate method to ensure that
  // MaxHashChainLen is inline. (Without this split OMS_Session and OMS_Context would
  // have to be included into this file to allow inlining)
  void ChangeMaxHashChainLen(int len);
  inline void MaxHashChainLen(int len){
    if (len > m_maxLen){
      m_maxLen = len;
      ChangeMaxHashChainLen(len);
    }
  }
};

/*----------------------------------------------------------------------*/
/* Implementation of inline methods of class OMS_OidHash              */
/*----------------------------------------------------------------------*/

inline tsp00_Int4 OMS_OidHash::GetHashSize() const
{
  return m_headentries;
}

/*===========================================================================*/
/*! The method inserts an object container into the hash. The counter of entries   
**  in the hash is increased and thereby it is checked whether a rehash should be
**  started. After the execution of the method, the member 
**  OMS_OidHash::m_headcurr is pointing to the bucket into which the insert 
**  was done. 
**  \param h Object container of the object to be inserted */
/*===========================================================================*/
inline void OMS_OidHash::HashInsert(OmsObjectContainer* h) 
{
  h->SetNext(*HeadPtr(h->m_oid));
	*m_headcurr     = h;
  IncCount();        // PTS  1118855
#ifdef _ASSERT_OMS
  HashCheck();
#endif
}

/*===========================================================================*/
/*! The method inserts an object container into the hash. Instead of computing
**  the bucket using the hash-function, the bucket to which the member 
**  OMS_OidHash::m_headcurr is pointing, is used.
**  The counter of entries in the hash is increased and thereby it is checked 
**  whether a rehash should be started.
**  \attention There is NO check, that this bucket is the right one for 
**  the given object! 
**  \param h Object container of the object to be inserted */
/*===========================================================================*/
inline void OMS_OidHash::HashSlotInsert (OmsObjectContainer* h) 
{
	h->SetNext(*m_headcurr);
	*m_headcurr     = h;
  IncCount();         // PTS  1118855
#ifdef _ASSERT_OMS
  HashCheck();
#endif
}

/*===========================================================================*/
/*! This method increases the counter of the current entries in the hash and 
**  if necessary it adapts the hash size. Rehashing is considered necessary, 
**  if the number of entries is larger than twice the size of the hash array 
**  (The hash chains have a mean length of 2). 
**  Rehashing will double the size of the hash array. 
** \since PTS 1118855 */
/*===========================================================================*/
inline void OMS_OidHash::IncCount()
{  
  ++m_count;

  if (m_count > m_maxCount){
    m_maxCount = m_count;
  }

  if (m_count > (m_headentries << 1)){
    HashResize(2 * m_headentries);
  }
}


/*===========================================================================*/
/*! This method decreases the counter of the current entries in the hash. This
**  information is needed for the dynamical adaptation of the hash size.
** \since PTS 1118855 */
/*===========================================================================*/
inline void OMS_OidHash::DecCount()
{  
  --m_count;
}


/*===========================================================================*/
/*! This method resets the number of entries to zero and if necessary it adapts 
**  the hash size. Rehashing is considered as necessary if the maximal number 
**  of entries is less than 1/4 of the number of hash buckets. Rehashing will 
**  reduce the size of the hash array by half. To reduce the overhead rehashing
**  is only considered if the current transaction has accessed at least one object 
**  and the size of the hash array is still larger than the constant MINIMAL_OMS_HASH_SIZE.
**  \param adaptOidHash If this parameter equals false, then it is not checked
**         whether an adaptation is necessary.
**  \since PTS 1118855 */
/*===========================================================================*/
inline void OMS_OidHash::ResetCount(bool adaptOidHash)
{  
  if (   adaptOidHash
      && m_maxCount != 0 
      && m_maxCount < (m_headentries >> 2) 
      && m_headentries > MINIMAL_OMS_HASH_SIZE){
    HashResize(m_headentries / 2, false);
  }

  m_count    = 0;
  m_maxCount = 0;
  m_maxLen   = 0;
}

/*----------------------------------------------------------------------*/

inline OMS_OidHash::OidIter OMS_OidHash::First ()
{
  OidIter iter(this);
  /* PTS 1122885 FF 2003-07-03 if there is no element within the OidHash don't iterate */
  for (iter.m_headIndex = 0; (0 < m_count) && (iter.m_headIndex < m_headentries); ++iter.m_headIndex) {
    iter.m_curr = this->m_head[iter.m_headIndex];
    if (NULL != iter.m_curr) 
    {
      iter.m_entries = 1;
      break;
    }
  }
  return iter;
}

/*----------------------------------------------------------------------*/

inline OMS_OidHash::OidIter::OidIter(OMS_OidHash* h) : m_hash(h), m_entries(0), m_curr(NULL), m_headIndex(-1) 
{
}

/*----------------------------------------------------------------------*/

inline OMS_OidHash::OidIter::OidIter(const OidIter& iter)
{
  *this = iter;
}

/*----------------------------------------------------------------------*/

inline OMS_OidHash::OidIter::~OidIter()
{
}

/*----------------------------------------------------------------------*/

inline void OMS_OidHash::OidIter::operator++() 
{
  if (NULL != m_curr) {
    m_curr = m_curr->GetNext();
  }
  if (NULL == m_curr) {
    ++m_headIndex; 
    while (m_headIndex < m_hash->m_headentries) {
      m_curr = m_hash->m_head[m_headIndex];
      if (NULL != m_curr) 
      {
        break;
      }
      ++m_headIndex;
    }
  }
  ++m_entries;
}

/*----------------------------------------------------------------------*/

inline void OMS_OidHash::OidIter::operator++(int dummy) 
{
  operator++();
}

/*----------------------------------------------------------------------*/

inline OMS_OidHash::OidIter::operator bool() const 
{
  return (m_curr != NULL);
}

/*----------------------------------------------------------------------*/

inline OmsObjectContainerPtr OMS_OidHash::OidIter::operator()() const 
{
  return m_curr;
}

/*===========================================================================*/
/*! This method searches the hash for the object identified by the given OID.
**  While traversing the hash chain, the length of the hash chain is cheched
**  against the current maximal hash chain length and if necessary this
**  monitoring information is updated (PTS 1118855).
**  \param k OID of the object to lookup
**  \param ignoreGeneration If true, then the object with the given pageNo and pagePos
**         and with the 'replace-flag' = false is returned (ignoring the generation).
**         If false, then the object with exaclty the same oid (incl. the generation)
**         is returned.
**  \return a pointer to the object container if object is found; otherwise NULL */
/*===========================================================================*/
inline OmsObjectContainerPtr OMS_OidHash::HashFind (const OmsObjectId* k, bool ignoreGeneration) 
{
	OmsObjectContainer* curr = *HeadPtr(*k);
  int cnt = 1;  // PTS 1118855

  if (ignoreGeneration){  // PTS 1125361
	  while (curr){
      if (!curr->ReplacedFlag() && curr->m_oid.equalExceptGeneration(*k)){
        MaxHashChainLen(cnt);  // PTS 1118855
			  return curr;
      }
      else {
			  curr = curr->GetNext();
        ++cnt;  // PTS 1118855
      }
    }
  }
  else {
    while (curr){
      if (curr->m_oid == *k){
        MaxHashChainLen(cnt);  // PTS 1118855
			  return curr;
      }
      else {
			  curr = curr->GetNext();
        ++cnt;  // PTS 1118855
      }
    }
  }

  MaxHashChainLen(cnt);  // PTS 1118855
	return NULL;
}


/*===========================================================================*/
/*! When a new keyed object is created with the same key as an other object, which was 
**  deleted in the same transaction, then the old one is marked as replaced. Therefore 
**  during the rollback of the 'NEW' such objects must be reactivated.
**  \param p Pointer to the frame of the object which should be rolled back.
**  \return Pointer to the object, if an object has been found and reactivated; 
**          otherwise NULL 
**  \since PTS 1125361
*/
/*===========================================================================*/
inline OmsObjectContainerPtr OMS_OidHash::ReactivateReplacedVersion(OmsObjectContainer* p)
{
  OmsObjectContainer* curr = *HeadPtr(p->m_oid);

	while (curr){
    if (curr->ReplacedFlag() && curr->m_oid.isDirectPredOf(p->m_oid)){
      curr->UnmarkReplaced();
			return curr;
    }
    else {
			curr = curr->GetNext();
    }
  }
  return NULL;
}


#endif // __OMS_OIDHASH_HPP