/*!
 * \file    OMS_ClassIdHash.cpp
 * \author  IvanS, MarkusSi, PeterG
 * \brief   Implementation of local container dictionary
 */
/*

    ========== licence begin  GPL
    Copyright (c) 2002-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



*/



#include "Oms/OMS_ClassIdHash.hpp"
#include "Oms/OMS_Session.hpp"
#include "Oms/OMS_DumpInterface.hpp"
#include "Oms/OMS_ObjectContainer.hpp"
#include "hsp77.h"
#include "geo573.h"
#include "Oms/OMS_Defines.h"

/*----------------------------------------------------------------------*/
/* Implementation of ClsId HashList                                     */
/*----------------------------------------------------------------------*/

OMS_ClassIdHash::OMS_ClassIdHash () 
: headentries(0)
, m_count(0)
, m_clsidHead(NULL)
, m_guidHead(NULL)
, freeListHeader(NULL)
, m_context(NULL) 
{
};

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

OMS_ClassIdEntry* OMS_ClassIdHash::AutoRegister (const ClassIDRef guid, OmsSchemaHandle Schema, OmsContainerNo ContainerNo) 
{
  OMS_ClassIdEntry* curr = NULL;
  OMS_ContainerInfo* pContainerInfo = NULL;
  try {                                                 // PTS 1120873
    pContainerInfo = OMS_Globals::FindContainerInfo(m_context->GetCurrentSession()->m_lcSink, guid, Schema, ContainerNo);
  } catch (DbpError &e){                                // PTS 1120873
    if (e_unknown_guid != e.dbpError())                 // PTS 1120873
      throw e;                                          // PTS 1120873
    else                                                // PTS 1120873
      ThrowUnknownContainer(guid, Schema, ContainerNo); // PTS 1120873
  }

  if (NULL != pContainerInfo) {
    curr = new(m_context) OMS_ClassIdEntry (pContainerInfo, m_context);
    if (NULL != curr) {
      HashInsert (curr);
    }
  }
  else {
    this->ThrowUnknownContainer(guid, Schema, ContainerNo);
  }
  return curr;
}

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

OMS_ClassIdEntry* OMS_ClassIdHash::AutoRegisterForReg (const ClassIDRef guid, OmsSchemaHandle Schema, OmsContainerNo ContainerNo) 
{
  OMS_ClassIdEntry*  curr = NULL;
  OMS_ContainerInfo* pContainerInfo = NULL;
  try {                                               // PTS 1120873
    pContainerInfo = OMS_Globals::FindContainerInfo(m_context->GetCurrentSession()->m_lcSink, guid, Schema, ContainerNo);
  } catch (DbpError &e){                                // PTS 1120873
    if (e_unknown_guid != e.dbpError())                 // PTS 1120873
      throw e;                                          // PTS 1120873
    else                                                // PTS 1120873
      ThrowUnknownContainer(guid, Schema, ContainerNo); // PTS 1120873
  }

  if (NULL != pContainerInfo) {
    curr = new(m_context) OMS_ClassIdEntry (pContainerInfo, m_context);
    if (NULL != curr) {
      HashInsert (curr);
    }
  }
  else {
    curr = NULL;
  }
  return curr;
}

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

void OMS_ClassIdHash::Clean() {
  // remove all infos of dropped containers
  OMS_ClassIdHash::Iter iter = this->First();
  while (iter) {
    OMS_ClassIdEntry* curr = iter();
    ++iter;
    if (curr->GetContainerInfoPtr()->IsDropped()) {
      this->HashDelete(curr);
    }
  }
}

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

// PTS 1117571
// Clear structures which are used for cached keys and cache misses.
// As the structure for the cached keys is also used for those objects
// which are newly created in a version, delete only those objects, which
// do not belong to a version. 
void OMS_ClassIdHash::ClearCachedKeys(OMS_Context* pContext) {

  OMS_ClassIdEntry*     pCurrContainer; 
  OMS_ClassIdHash::Iter containerIter  = First();
  while (containerIter) {
    pCurrContainer = containerIter();
    if (pCurrContainer->UseCachedKeys() && pCurrContainer->IsKeyedObject()) {
      // Reset structure for cache hits 
      if (!m_context->IsVersion()) {
        // As we are not in a version, objects which are created in a version
        // cannot exist. Therefore the complete structure can be reset
        pCurrContainer->VersionDelIndex(false, pContext);
      }
      else {
        // Delete only those entries which are not created in a version
        pCurrContainer->VersionDelIndex(true, pContext);
      }

      // Reset structure for cache misses if requested
      pCurrContainer->DropCacheMisses(pContext);
    }
    ++containerIter;
  }
}

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

void OMS_ClassIdHash::Create (OMS_Context* context) {
#define HEAD_ENTRIES 257 
  m_context   = context;
  m_clsidHead = REINTERPRET_CAST(OMS_ClassIdEntryPtr*, 
    m_context->allocate(sizeof(OMS_ClassIdEntryPtr) * HEAD_ENTRIES));
  m_guidHead = REINTERPRET_CAST(OMS_ClassIdEntryPtr*, 
    m_context->allocate(sizeof(OMS_ClassIdEntryPtr) * HEAD_ENTRIES));
  headentries = HEAD_ENTRIES;
  for (tsp00_Int4 ix = 0; ix < headentries; ++ix) {
    m_clsidHead[ix]= NULL;
    m_guidHead[ix] = NULL;
  };
  m_classDir.Create(context);
  freeListHeader = new(m_context) OMS_FreeListHeader(0 /* PTS 1109561 */);
};

// end constructor

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

void OMS_ClassIdHash::Dump(OMS_DumpInterface& dumpObj) const
{
  struct ClsIdHashDumpInfo 
  {
    OMS_ClassIdHash*      m_this;
    OMS_ClassIdEntryPtr*  m_clsidHead;
    OMS_ClassIdEntryPtr*  m_guidHead;
    OMS_FreeListHeader* m_freeListHeader;
    OMS_Context*      m_context;
    tsp00_Int4            m_headentries;
  } clsIdHashDumpInfo;

  dumpObj.SetDumpLabel(LABEL_OMS_LOCAL_CLS_DIR);
  clsIdHashDumpInfo.m_this           = CONST_CAST(OMS_ClassIdHash*, this);
  clsIdHashDumpInfo.m_clsidHead      = m_clsidHead;
  clsIdHashDumpInfo.m_guidHead       = m_guidHead;
  clsIdHashDumpInfo.m_freeListHeader = freeListHeader;
  clsIdHashDumpInfo.m_context        = m_context;
  clsIdHashDumpInfo.m_headentries    = headentries;
  dumpObj.Dump(&clsIdHashDumpInfo, sizeof(clsIdHashDumpInfo));

  struct ClsIdEntryDumpInfo 
  {
    OMS_ClassIdEntryPtr       m_this;
    OMS_ClassIdEntry*         m_clsidHashNext;
    OMS_ClassIdEntry*         m_guidHashNext;
    OMS_ContainerInfo*       m_containerInfo;
    OMS_FreeListHeader*     m_freeHead;
    OMS_FreeListHeader*     m_freeHeadInVersion;
  } clsIdEntryDumpInfo;

  dumpObj.SetDumpLabel(LABEL_OMS_LOCAL_CLS_HASH);
  int ix;
  for (ix = 0; ix < headentries; ++ix) 
  {
    OMS_ClassIdEntryPtr curr = m_clsidHead[ix];
    while (NULL != curr)
    {
      clsIdEntryDumpInfo.m_this          = curr;
      clsIdEntryDumpInfo.m_clsidHashNext = curr->m_clsidHashNext;
      clsIdEntryDumpInfo.m_guidHashNext  = curr->m_guidHashNext;
      clsIdEntryDumpInfo.m_containerInfo = curr->GetContainerInfoPtr();
      clsIdEntryDumpInfo.m_freeHead      = curr->m_freeHead;
      clsIdEntryDumpInfo.m_freeHeadInVersion = curr->m_freeHeadInVersion;
      dumpObj.Dump(&clsIdEntryDumpInfo, sizeof(clsIdEntryDumpInfo));
      curr = curr->m_clsidHashNext;
    }
  }
  dumpObj.SetDumpLabel(LABEL_OMS_LOCAL_GUID_HASH);
  for (ix = 0; ix < headentries; ++ix) 
  {
    OMS_ClassIdEntryPtr curr = m_guidHead[ix];
    while (NULL != curr)
    {
      clsIdEntryDumpInfo.m_this          = curr;
      clsIdEntryDumpInfo.m_clsidHashNext = curr->m_clsidHashNext;
      clsIdEntryDumpInfo.m_guidHashNext  = curr->m_guidHashNext;
      clsIdEntryDumpInfo.m_containerInfo = curr->GetContainerInfoPtr();
      clsIdEntryDumpInfo.m_freeHead      = curr->m_freeHead;
      clsIdEntryDumpInfo.m_freeHeadInVersion = curr->m_freeHeadInVersion;
      dumpObj.Dump(&clsIdEntryDumpInfo, sizeof(clsIdEntryDumpInfo));
      curr = curr->m_guidHashNext;
    }
  }
  m_classDir.Dump(dumpObj);
}

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

OMS_FreeListHeader* OMS_ClassIdHash::GetFreeListHeader(size_t size) 
{
  OMS_FreeListHeader* p = freeListHeader;
  while (p != NULL) {
    if (p->size == size) {
      return p;
    }
    else {
      p = p->next;
    }
  }
  p       = new(m_context) OMS_FreeListHeader(size);
  p->next = freeListHeader->next;
  freeListHeader->next = p;
  return p;
}

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

void OMS_ClassIdHash::HashDelete (OMS_ClassIdEntry* containerInfo) {
  // remove from ContainerHandle Hash 
  int hashSlot              = containerInfo->GetContainerHandle() % headentries;
  OMS_ClassIdEntryPtr* prev = &m_clsidHead[hashSlot];
  OMS_ClassIdEntryPtr  curr = *prev;
  while (curr != NULL) {
    if (curr == containerInfo) {
      --m_count;
      *prev = curr->m_clsidHashNext;
      break;
    }
    else {
      prev = &curr->m_clsidHashNext;
      curr = curr->m_clsidHashNext;
    }
  }
  // remove from Guid,Schema,ContainerNo Hash
  hashSlot = HashValue(containerInfo->GetGuid(), containerInfo->GetSchema(), 
    containerInfo->GetContainerNo(), headentries);
  prev     = &m_guidHead[hashSlot];
  curr     = *prev;
  while (curr != NULL) {
    if (curr == containerInfo) {
      *prev = curr->m_guidHashNext;
      break;
    }
    else {
      prev = &curr->m_guidHashNext;
      curr = curr->m_guidHashNext;
    }
  }
  // delete local ContainerInfo
  containerInfo->DeleteSelf(m_context);
}

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

// Method is doing the same as HashDelete with the exception, that the whole
// hash-array is searched instead of only a single hash-slot. This method is
// needed if the pointer to the global directory entry is not longer accessible
// (see PTS 1137709)
void OMS_ClassIdHash::HashDelete_Slow (OMS_ClassIdEntry *pDel) 
{
  // remove from ContainerHandle Hash 
  for (int i=0; i<headentries; ++i){
    OMS_ClassIdEntryPtr *prev = &m_clsidHead[i];
    OMS_ClassIdEntryPtr  curr = *prev;
    while (curr) {
      if (curr == pDel) {
        --m_count;
        *prev = curr->m_clsidHashNext;
        break;
      }
      else {
        prev = &curr->m_clsidHashNext;
        curr = curr->m_clsidHashNext;
      }
    }
  }

  // remove from Guid,Schema,ContainerNo Hash
  for (int j=0; j<headentries; ++j){
    OMS_ClassIdEntryPtr *prev     = &m_guidHead[j];
    OMS_ClassIdEntryPtr  curr     = *prev;
    while (curr) {
      if (curr == pDel) {
        *prev = curr->m_guidHashNext;
        break;
      }
      else {
        prev = &curr->m_guidHashNext;
        curr = curr->m_guidHashNext;
      }
    }
  }
}

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

bool OMS_ClassIdHash::IsBaseClassOf(const ClassIDRef baseGuid, const ClassIDRef derivedGuid) {
  return  IsDerivedClassOf(baseGuid, derivedGuid);
}

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

bool OMS_ClassIdHash::IsDerivedClassOf(const ClassIDRef baseGuid, const ClassIDRef derivedGuid) {
  OMS_GuidEntry* pDerivedClassInfo = GetClassInfo(derivedGuid);
  do {
    if (pDerivedClassInfo->IsDerivedClassOf(baseGuid)) {
      return true;
    }
    if (pDerivedClassInfo->IsDerivedClass()) {
      pDerivedClassInfo = GetClassInfo(pDerivedClassInfo->GetBaseGuid());
    }
    else {
      return false;
    }
  }
  while(1);
}

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

void OMS_ClassIdHash::Resize()
{
    OMS_ClassIdEntryPtr*  oldClsIdHead = m_clsidHead;
    OMS_ClassIdEntryPtr*  oldGuidHead  = m_guidHead;
    try
    {
        int ix;
        // calculate new header size
        int newHeadEntries = m_count - 1;
        bool     isPrime   = false;
        while (!isPrime) {
            newHeadEntries++;
            isPrime = (1 == (newHeadEntries % 2)) ? true : false; 
            ix = 3;
            while (((ix * ix) <= newHeadEntries) && (isPrime)) {
                isPrime = (0 == (newHeadEntries % ix)) ? false : true;
                ix += 2;
            }
        }
        // allocate and init new headers
        m_clsidHead = NULL;
        m_clsidHead = REINTERPRET_CAST(OMS_ClassIdEntryPtr*, 
            m_context->allocate(sizeof(OMS_ClassIdEntryPtr) * newHeadEntries));
        m_guidHead = REINTERPRET_CAST(OMS_ClassIdEntryPtr*, 
            m_context->allocate(sizeof(OMS_ClassIdEntryPtr) * newHeadEntries));
        int oldHeadEntries = headentries;
        headentries        = newHeadEntries;
        for (ix = 0; ix < headentries; ++ix) {
            m_clsidHead[ix]= NULL;
            m_guidHead[ix] = NULL;
        };
        // rehash 
        m_count = 0;
        for (ix = 0; ix < oldHeadEntries; ++ix)
        {
            OMS_ClassIdEntry* next = oldClsIdHead[ix];
            while (next)
            {
                OMS_ClassIdEntry* curr = next; 
                next = next->m_clsidHashNext;
                int hashSlot = (curr->GetContainerHandle()) % headentries;
                curr->m_clsidHashNext  = m_clsidHead[hashSlot];
                m_clsidHead[hashSlot]  = curr;
                hashSlot = HashValue(curr->GetGuid(), curr->GetSchema(), curr->GetContainerNo(), headentries);
                curr->m_guidHashNext = m_guidHead[hashSlot];
                m_guidHead[hashSlot] = curr;
            }
        }
        // free old headers
        m_context->deallocate(oldClsIdHead);
        m_context->deallocate(oldGuidHead);
    }
    catch (BAD_ALLOC_GEO573&)
    {
      DbpErrorCallbackInterface *pCBInterface = DbpError::dbpGetCallback();  // PTS 1127871
      if (pCBInterface){
        pCBInterface->dbpCaughtBadAlloc();
      }

      // reset old state in case of missing memory
      m_context->deallocate(m_clsidHead);
      m_clsidHead = oldClsIdHead;
      m_guidHead  = oldGuidHead;
    }
}


void OMS_ClassIdHash::ThrowUnknownContainer(OMS_ClassIdEntryPtr containerInfo) {
  this->ThrowUnknownContainer(containerInfo->GetGuid(), containerInfo->GetSchema(), containerInfo->GetContainerNo());
}

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


void OMS_ClassIdHash::ThrowUnknownContainer(const ClassIDRef guid, OmsSchemaHandle Schema, OmsContainerNo ContainerNo) {
  char buf[256];
  OmsTypeWyde         Identifier[OMS_MAX_SCHEMA_NAME_LENGTH];
  tgg00_BasisError    DBError;
  m_context->LcSink()->GetSchemaName(Schema, &Identifier[0], &DBError);
  if (0 != DBError)
  {
#ifdef LIVECACHE_INTGUIDS
    //sprintf (buf, "guid : %8.8X, Schema(%8X) CNo(%8X)", guid, Schema, ContainerNo);                 // PTS 1125307
    sp77sprintf (buf, sizeof(buf),"guid : %#8.8X, Schema(%d) CNo(%d)", guid, Schema, ContainerNo);   // PTS 1125307
#else
    //sprintf (buf, "guid : %8.8X-%4.4X-%4.4X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X, Schema(%8X) CNo(%8X)", guid.Data1, guid.Data2, guid.Data3,
    //  guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
    //  guid.Data4[6], guid.Data4[7], Schema, ContainerNo);                                           // PTS 1125307
    sp77sprintf (buf, sizeof(buf),"guid : %8.8X-%4.4X-%4.4X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X, Schema(%d) CNo(%d)", guid.Data1, guid.Data2, guid.Data3,
      guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
      guid.Data4[6], guid.Data4[7], Schema, ContainerNo);                                             // PTS 1125307
#endif
  }
  else
  { 
#ifdef LIVECACHE_INTGUIDS
    sp77sprintfUnicode(sp77encodingUTF8, buf, sizeof(buf), 
      "guid : %#8.8X, Schema(%d:'%S') CNo(%d)", guid, Schema, &Identifier[0], ContainerNo);
#else
    sp77sprintfUnicode(sp77encodingUTF8, buf, sizeof(buf), 
      "guid : %8.8X-%4.4X-%4.4X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X, Schema(%d:'%S') CNo(%d)", guid.Data1, guid.Data2, guid.Data3,
      guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
      guid.Data4[6], guid.Data4[7], Schema, &Identifier[0], ContainerNo);
#endif
  }
  OMS_Globals::Throw(DbpError (DbpError::DB_ERROR, e_unknown_guid, buf, __MY_FILE__, __LINE__)); 
}

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

void OMS_ClassIdHash::UnregisterAll() /* PTS 1110149 */
{
  /* delete hash */
  OMS_ClassIdEntry* curr;
  OMS_ClassIdEntry* delClsInfo;
  for (tsp00_Int4 ix = 0; ix < headentries; ix++) {
    curr = m_clsidHead[ix];
    m_clsidHead[ix] = NULL;
    m_guidHead[ix] = NULL;
    while (curr != NULL) {
      delClsInfo = curr;
      curr       = curr->m_clsidHashNext;
      delClsInfo->DeleteSelf(m_context);
    }
  }
  /* delete free list headers except sentinel */
  if (0 != freeListHeader)
  {
    OMS_FreeListHeader* toDel;
    OMS_FreeListHeader* p = freeListHeader->next;
    freeListHeader->next = NULL;
    while (p != NULL) {
      toDel = p;
      p     = p->next;
      toDel->DeleteSelf(m_context);
    }
  }
  m_classDir.~OMS_ClassHash();
  m_classDir.Create(m_context);
}

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

OMS_ClassIdHash::~OMS_ClassIdHash () 
{
  if (NULL != m_context)
  {
    this->UnregisterAll();
    if (0 != freeListHeader)
    {
      freeListHeader->DeleteSelf(m_context);
    }
    m_context->deallocate(m_clsidHead);
    m_context->deallocate(m_guidHead);
  }
  new(this) OMS_ClassIdHash();
};

// end destructor

