//==================================================================================================
// S t a n d a r d                                                                        Interface
// M e m o r y
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library 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 Library General Public License for more details (http://www.gnu.org).

/*DESCRIPTION*/
/* This module provides facilities to manage the memory allocation and deallocation. */

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "standard/memory.hpp"

// Guardian //--------------------------------------------------------------------------------------
#ifndef guStandardMemory
#define guStandardMemory

// Headers //---------------------------------------------------------------------------------------
#include <new> /*INCLUDE*/
#include <bpp/standard/class.hpp> /*INCLUDE*/
#include <bpp/standard/stream.hpp> /*INCLUDE*/

namespace bpp {

// Importation/Exportation //-----------------------------------------------------------------------
#ifdef STANDARD_DLL
 #define dll_export DLL_EXPORT
#else
 #define dll_export DLL_IMPORT
#endif

// Namespaces //------------------------------------------------------------------------------------
#define public_area  standardMemory
#define private_area standardMemory_private

namespace public_area {
 /*NAMESPACE*/ using namespace standardClass;
 /*NAMESPACE*/ using namespace standardStream;
}

namespace private_area { using namespace public_area; }

extern_module_name;

// Initialization //--------------------------------------------------------------------------------
#define iniStandardMemory
has_initializer;

// Macrocommands //---------------------------------------------------------------------------------
#ifdef MEMORY_SPY_MODE
 /*MACROCOMMAND*/
 /* The expression <CODE>prLine</CODE> is integrated into the source code if
    <CODE>MEMORY_SPY_MODE</CODE> is defined. */
 #define memory_spy_mode(prLine) prLine

 /*MACROCOMMAND*/ /* Allocates a new object. */
 #define new_object(prCommand)                                              \
  ( standardMemory_private::registerPointer(new prCommand,#prCommand,       \
                                            __FILE__ ,__LINE__,false,nil) ) \

 /*MACROCOMMAND*/ /* Allocates an array of objects. */
 #define new_array(prCommand,prSize)                                                        \
  ( standardMemory_private::registerPointer(new prCommand[prSize],#prCommand " x " #prSize, \
                                            __FILE__,__LINE__,true,nil) )                   \

 /*MACROCOMMAND*/ /* Deallocates an object. */
 #define delete_object(prPointer)                                           \
  ( delete standardMemory_private::unregisterPointer(prPointer,false,nil) ) \

 /*MACROCOMMAND*/ /* Deallocates an array of objects. */
 #define delete_array(prPointer)                                              \
  ( delete [] standardMemory_private::unregisterPointer(prPointer,true,nil) ) \

 /*MACROCOMMAND*/ /* Allocates a new object using a pool. */
 #define new_pool_object(prPool,prCommand)                                                   \
  ( standardMemory_private::registerPointer(new((prPool).allocateObject()) prCommand,        \
                                            #prCommand,__FILE__ ,__LINE__,false,&(prPool)) ) \

 /*MACROCOMMAND*/ /* Deallocates an object using a pool. */
 #define delete_pool_object(prPool,prPointer)                                                     \
  ( (prPool).deleteObject(standardMemory_private::unregisterPointer(prPointer,false,&(prPool))) ) \

#else
 #define memory_spy_mode(prLine)

 #define new_object(prCommand)                ( new prCommand )
 #define new_array(prCommand,prSize)          ( new prCommand[prSize] )
 #define new_pool_object(prPool,prCommand)    ( new((prPool).allocateObject()) prCommand )
 #define delete_object(prPointer)             ( delete (prPointer) )
 #define delete_array(prPointer)              ( delete [] (prPointer) )
 #define delete_pool_object(prPool,prPointer) ( (prPool).deleteObject(prPointer) )
#endif

// Types & Classes //-------------------------------------------------------------------------------
namespace public_area {
 //------------------------------------------------------------------------------------------Classes
 class clAbstractPool;

 template <class prType> class clPool;
 //-----------------------------------------------------------------------------------Constant Types
 typedef const clAbstractPool ctAbstractPool;
}

namespace private_area {
 memory_spy_mode (
  class clPointerInformation;

  typedef std_map(tyPointer,clPointerInformation *) clPointerRegister;

  typedef const clPointerRegister    ctPointerRegister;
  typedef const clPointerInformation ctPointerInformation;
 )
}

// Functions Interface //---------------------------------------------------------------------------
namespace public_area { memory_spy_mode ( function void outPointerRegister(clOutStream &); ) }

namespace private_area {
 memory_spy_mode (
  template <class prType>
  prType * registerPointer(prType *,tcString,tcString,tyCardinal,tyBoolean,clAbstractPool *);

  template <class prType> prType * unregisterPointer(prType *,tyBoolean,clAbstractPool *);
 )

 function void throwError(void);

 testing_mode ( function void test(void); )
}

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 /*ERROR*/ extern_error erMemory; /* Not enough memory. */

 memory_spy_mode (
  /*ERROR*/ extern_error erPointerAlreadyRegistered; /* The pointer is already registered. */

  /*ERROR*/ extern_error erPointerNotRegistered;
  /* The pointer is not registered. That means the area you try to deallocate has not been
     allocated. */

  /*ERROR*/ extern_error erWrongDestructionMode;
  /* Wrong way to destroy an array or a single object. */

  /*ERROR*/ extern_error erWrongDestructionPool; /* Wrong pool used to destroy an object. */
 )
}

// Constants & Variables //-------------------------------------------------------------------------
memory_spy_mode (
 /*VARIABLE*/
 /* List of the pointers to the dynamically allocated objects and arrays of objects. */
 extern_dynamic_constant(public,clPointerRegister,goPointerRegister,pointerRegister);

 extern_static_constant(private,tcString,goPointerFileName,?);
 extern_static_constant(private,clMutex,goMutex,?);
)

// A b s t r a c t P o o l  Interface //------------------------------------------------------------
namespace public_area {
 /*CLASS clAbstractPool */
 /* Represents a pool of objects. Instead of allocating memory for several objects separately,
    a pool allocates continuous blocks of memory (that can contain several objects) and manages
    then the assignment of this memory to each object. It is an abstract class. */
 class clAbstractPool : public clBaseObject {
  //-----------------------------------------------------------------------------------------Private
  private_property constructor clAbstractPool(ctAbstractPool &);
  private_property clAbstractPool & operator = (ctAbstractPool &);
  //------------------------------------------------------------------------------------------Public
  public_property constructor        clAbstractPool(void);
  public_property virtual destructor clAbstractPool(void);

  /*AMETHOD clAbstractPool */
  /* Allocates memory for an object (no constructor is called). Abstract method. */
  public_property virtual tyPointer allocateObject(void) = 0;

  /*AMETHOD clAbstractPool */
  /* Destructs an object (its destructor is called). Abstract method. */
  public_property virtual void deleteObject(tyPointer) = 0;
 };
}

// P o o l  Interface //----------------------------------------------------------------------------
namespace public_area {
 /*CLASS clPool */ /* Represents a pool of <CODE>prType</CODE> objects. */
 template <class prType> class clPool : public clAbstractPool {
  //-----------------------------------------------------------------------------------------Private
  private_property typedef struct _cpBlock_ {
   prType      element;
   _cpBlock_ * next;
  } cpBlock;

  private_property std_vector(cpBlock *) atBlockS;
  private_property tyCardinal            atBlockSize;
  private_property tyCardinal            atCounter;
  private_property cpBlock *             atHead;

  private_property constructor clPool(const clPool &);
  private_property clPool & operator = (const clPool &);
  //------------------------------------------------------------------------------------------Public
  public_property constructor clPool(tyCardinal=1024);
  public_property destructor  clPool(void);

  public_property tyPointer allocateObject(void);
  public_property void      deleteObject(tyPointer);
 };
}

// P o i n t e r I n f o r m a t i o n  Interface //------------------------------------------------
namespace private_area {
 memory_spy_mode (
  class clPointerInformation {
   //----------------------------------------------------------------------------------------Private
   private_property typedef clAbstractPool * tyPool;

   private_property constructor clPointerInformation(ctPointerInformation &);
   private_property clPointerInformation & operator = (ctPointerInformation &);
   //-----------------------------------------------------------------------------------------Public
   read_only_attribute(tcString,atCommand,command);
   read_only_attribute(tcString,atType,type);
   read_only_attribute(tcString,atFile,file);
   read_only_attribute(tyCardinal,atLine,line);
   read_only_attribute(tyBoolean,atArray,array);
   read_only_attribute(tyPool,atPool,pool);

   public_property constructor clPointerInformation(tcString,tcString,tcString,
                                                    tyCardinal,tyBoolean,tyPool);

   public_property destructor clPointerInformation(void) {}
  };
 )
}

// Functions Inline //------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// A b s t r a c t P o o l  Inline //---------------------------------------------------------------
namespace public_area {
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clAbstractPool */ /* Builds a pool. */
 inline clAbstractPool::clAbstractPool(void) : clBaseObject() {}
}

// P o o l  Inline //-------------------------------------------------------------------------------
namespace public_area {
 //--------------------------------------------------------------------------------------Constructor
 /*METHOD clPool */
 /* Builds a new pool. The size, i.e. the number of <CODE>prType</CODE> objects, of a block can be
    provided (default is 1024). */
 template <class prType> inline clPool<prType>::clPool(tyCardinal agBlockSize)
 : clAbstractPool(),atBlockS(),atBlockSize(agBlockSize),atCounter(0),atHead(nil) {}
}

// P o i n t e r I n f o r m a t i o n  Inline //---------------------------------------------------
#ifdef MEMORY_SPY_MODE
namespace private_area {
 //--------------------------------------------------------------------------------------Constructor
 inline clPointerInformation::clPointerInformation(tcString agCommand,tcString agType,
                                                   tcString agFile,tyCardinal agLine,
                                                   tyBoolean agArray,tyPool agPool)
 : atCommand(agCommand),atType(agType),atFile(agFile),atLine(agLine),atArray(agArray),
   atPool(agPool) {}
}
#endif

// Functions Implementation //----------------------------------------------------------------------
namespace private_area {
 memory_spy_mode (
  //---------------------------------------------------------------------------------RegisterPointer
  template <class prType>
  prType * registerPointer(prType * agPointer,tcString agCommand,tcString agFile,
                           tyCardinal agLine,tyBoolean agArray,clAbstractPool * agPool) {
   method_name("registerPointer");

   safe_yes ( private_area::goMutex.lock(); )

   if (goPointerRegister->count(agPointer) == 1) {
    safe_yes ( private_area::goMutex.release(); )
    send_error(erPointerAlreadyRegistered);
   }

   private_area::goPointerRegister->insert(std_make_pair(agPointer,
   new clPointerInformation(agCommand,typeid(*agPointer).name(),agFile,agLine+1,agArray,agPool)));

   safe_yes ( private_area::goMutex.release(); )
   return (agPointer);
  }
  //-------------------------------------------------------------------------------UnregisterPointer
  template <class prType>
  prType * unregisterPointer(prType * agPointer,tyBoolean agArray,clAbstractPool * agPool) {
   method_name("unregisterPointer");

   clPointerInformation * lcPointerInformation;

   safe_yes ( private_area::goMutex.lock(); )

   if (goPointerRegister->count(agPointer) == 0) {
    safe_yes ( private_area::goMutex.release(); )
    send_error(erPointerNotRegistered);
   }

   lcPointerInformation=(*(private_area::goPointerRegister->find(agPointer))).second;

   if (agArray!=lcPointerInformation->array()) {
    safe_yes ( private_area::goMutex.release(); )
    send_error(erWrongDestructionMode);
   }

   if (agPool!=lcPointerInformation->pool()) {
    safe_yes ( private_area::goMutex.release(); )
    send_error(erWrongDestructionPool);
   }

   delete lcPointerInformation;
   private_area::goPointerRegister->erase(agPointer);
   safe_yes ( private_area::goMutex.release(); )
   return (agPointer);
  }
 )
}

namespace private_area {}

// P o o l  Implementation //-----------------------------------------------------------------------
namespace public_area {
 //---------------------------------------------------------------------------------------Destructor
 /*METHOD clPool */ /* Destructs the pool. */
 template <class prType> clPool<prType>::~clPool(void) {
  tyCardinal lcCounter = atBlockS.size();

  while (lcCounter>0) {
   --lcCounter;
   delete_array(reinterpret_cast<tyByte *>(atBlockS[lcCounter])-1);
  }
 }
 //-----------------------------------------------------------------------------------AllocateObject
 /*METHOD clPool */ /* Allocates memory for an object (no constructor is called). */
 template <class prType> tyPointer clPool<prType>::allocateObject(void) {
  tyByte * lcPointer;

  if (atHead==nil) {
   if (atCounter==0) {
    lcPointer=new_array(tyByte,1+atBlockSize*sizeof(cpBlock))+1;
    atBlockS.push_back(reinterpret_cast<cpBlock *>(lcPointer));
    atCounter=atBlockSize-1;
    atHead=nil;
    return (atBlockS.back()+atCounter);
   }
   else return (atBlockS.back()+(--atCounter));
  }

  lcPointer=reinterpret_cast<tyByte *>(atHead);
  atHead=atHead->next;
  return (lcPointer);
 }
 //-------------------------------------------------------------------------------------DeleteObject
 /*METHOD clPool */ /* Destructs an object (its destructor is called). */
 template <class prType> void clPool<prType>::deleteObject(tyPointer agPointer) {
  cpBlock * lcBlock = reinterpret_cast<cpBlock *>(agPointer);

  reinterpret_cast<prType *>(agPointer)->~prType();
  lcBlock->next=atHead;
  atHead=lcBlock;
 }
}

// End //-------------------------------------------------------------------------------------------
}
#undef dll_export
#undef public_area
#undef private_area
#endif
 
//==================================================================================================
// S t a n d a r d                                                                   Implementation
// M e m o r y
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This file is part of the B++ Library. This library is free software; you can redistribute it
// and/or modify it under the terms of the GNU Library General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your option) any later
// version.
//
// This library 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 Library General Public License for more details (http://www.gnu.org).

// File Name //-------------------------------------------------------------------------------------
#line __LINE__ "standard/memory.cpp"

// DLL Belonging //---------------------------------------------------------------------------------
#define STANDARD_DLL

// Headers //---------------------------------------------------------------------------------------
#include <bpp/file_name.hpp> /*NEED*/
#include <bpp/standard/memory.hpp> /*INTERFACE*/

namespace bpp {

// Namespaces //------------------------------------------------------------------------------------
#define public_area  standardMemory
#define private_area standardMemory_private
#define dll_export   DLL_EXPORT

namespace public_area  {}
namespace private_area {}

static_module_name("Standard/Memory");

// Initialization //--------------------------------------------------------------------------------
#undef iniStandardMemory
static_constant(private_area::clInitializer,goInitializer);

// Errors //----------------------------------------------------------------------------------------
namespace public_area {
 static_error erMemory;

 memory_spy_mode (
  static_error erPointerAlreadyRegistered;
  static_error erPointerNotRegistered;
  static_error erWrongDestructionMode;
  static_error erWrongDestructionPool;
 )
}

// Constants & Variables //-------------------------------------------------------------------------
memory_spy_mode (
 dynamic_constant(clPointerRegister,goPointerRegister);
 static_constant(tcString,goPointerRegisterFileName);
 static_constant(clMutex,goMutex);

 namespace public_area { function void (* outPointerContent)(clOutStream &,tyPointer); }
)

// Static Members //--------------------------------------------------------------------------------
namespace public_area  {}
namespace private_area {}

// Functions Implementation //----------------------------------------------------------------------
namespace public_area {
 memory_spy_mode (
  //------------------------------------------------------------------------------OutPointerRegister
  /*FUNCTION*/
  /* Writes into a stream the current list of dynamically allocated objects and arrays of
     objects. */
  function void outPointerRegister(clOutStream & agStream) {
   method_name("outPointerRegister");

   using namespace private_area;

   safe_yes ( private_area::goMutex.lock(); )

   clPointerRegister::const_iterator lcCurrentRegistration = goPointerRegister->begin();
   clPointerRegister::const_iterator lcLastRegistration    = goPointerRegister->end();

   while (lcCurrentRegistration!=lcLastRegistration) {
    agStream << end_line;
    agStream << "    Address = " << (*lcCurrentRegistration).first << end_line;
    agStream << "    Command = " << (*lcCurrentRegistration).second->command() << end_line;
    agStream << "    Type = " << demangleType((*lcCurrentRegistration).second->type()) << end_line;
    agStream << "    File = " << (*lcCurrentRegistration).second->file() << end_line;
    agStream << "    Line = " << (*lcCurrentRegistration).second->line() << end_line;

    if ((*lcCurrentRegistration).second->pool()!=nil)
     agStream << "    Pool = " << (*lcCurrentRegistration).second->pool() << end_line;

    if (outPointerContent!=nil) {
     agStream << "    Content =" << end_line;
     outPointerContent(agStream,(*lcCurrentRegistration).first);
    }

    lcCurrentRegistration++;
   }

   safe_yes ( private_area::goMutex.release(); )
   if (agStream.fail()) send_error(erStreamWriting);
  }
 )
}

namespace private_area {
 //---------------------------------------------------------------------------------------ThrowError
 function void throwError(void) {
  method_name("new");

  send_error(erMemory);
 }
}

// A b s t r a c t P o o l  Implementation //-------------------------------------------------------
namespace public_area {
 //---------------------------------------------------------------------------------------Destructor
 /*METHOD clAbstractPool */ /* Destructs the pool. */
 clAbstractPool::~clAbstractPool(void) {}
}

// I n i t i a l i z e r  Implementation //---------------------------------------------------------
namespace private_area {
 //--------------------------------------------------------------------------------------------Start
 property void clInitializer::start(void) {
  if (atCounter++ == 0) {
   try {
    #include <bpp/modules.hpp> /*NEED*/
    registerStop(this);
    environment::informInitialization(goModuleName);

    erMemory.create("Not enough memory.");

    memory_spy_mode (
     erPointerAlreadyRegistered.create("The pointer is already registered.");
     erPointerNotRegistered.create("The pointer isn't registered.");
     erWrongDestructionMode.create("Wrong way to destroy an array or a single object.");
     erWrongDestructionPool.create("Wrong pool used to destroy an object.");
    )

    std::set_new_handler((std::new_handler)throwError);

    memory_spy_mode (
     goPointerRegister         = new clPointerRegister;
     goPointerRegisterFileName = POINTER_REGISTER_FILE;

     outPointerContent = nil;
    )
   }

   initializer_catch;
  }
 }
 //---------------------------------------------------------------------------------------------Stop
 property void clInitializer::stop(void) {
  clOutFile lcFout;

  try {
   environment::informTermination(goModuleName);

   memory_spy_mode (
    method_name("initializer::destructor");

    if (not goPointerRegister->empty()) {
     using namespace environment;

     nextLine();
     out("[!] Warning: Some of the memory has been lost.",true,true);
     out("For more details, see the '",false,true); out(goPointerRegisterFileName);
     out("' file.",true);
     nextLine();

     open(lcFout,goPointerRegisterFileName,ios::out|ios::trunc);
     lcFout << "===============================================================================" << end_line;
     lcFout << " P o i n t e r s   R e g i s t e r                       Debugging Information" << end_line;
     lcFout << "===============================================================================" << end_line;
     lcFout << end_line;
     environment::outInformation(lcFout);
     lcFout << end_line;
     environment::outContext(lcFout);
     lcFout << end_line;
     lcFout << "[>] Lost Memory Blocks:" << end_line;
     outPointerRegister(lcFout);
     lcFout << end_line;
     lcFout << "===============================================================================" << end_line;
     close(lcFout);
     if (lcFout.fail()) send_error(erFileWriting);
    }

    delete goPointerRegister;
   )
  }

  initializer_catch;
 }
}

// End //-------------------------------------------------------------------------------------------
}