// objectix.cpp
// 
// Copyright (c) 1994 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
// 
// Initial Contributors:
// Accenture - Initial contribution
//

/** 
@file
@internalTechnology
*/

#include <kern_priv.h>
#include "dobject.h"
#define DObjectIx DObjectIxNinePointTwoHack // So that we're not redefining DObjectIx

/**	Creates a new kernel object index.

	@param	aPtr	Value to pass to Close() when objects are closed due to index deletion.

	@return	The pointer to the new index, NULL if out of memory.

    @pre    Calling thread must be in a critical section.
    @pre    No fast mutex can be held.
	@pre	Call in a thread context.
 */
DObjectIx *DObjectIx::New(TAny* aPtr)
	{
	return new DObjectIx(aPtr);
	}

#ifndef DOBJECT_TEST_CODE
void DObjectIx::Wait()
	{
	Kern::MutexWait(*HandleMutex);
	}

void DObjectIx::Signal()
	{
	Kern::MutexSignal(*HandleMutex);
	}
#endif


/**	Construct a kernel object index
 */
DObjectIx::DObjectIx(TAny *aPtr)
	: iNextInstance(1), iPtr(aPtr), iFree(-1)
	{
	}


/**	Destroys a kernel object index.

	Any objects in the index are closed. The iPtr member of the index is passed
	as the parameter to Close() for each object.

    @pre    Calling thread must be in a critical section.
    @pre    No fast mutex can be held.
	@pre	Call in a thread context.
 */
DObjectIx::~DObjectIx()
	{
	// We have to be very careful here. Calling Close() on the objects in the array
	// may result in other entries being removed from the array before we delete
	// them here, and may result in the array being ReAlloc()ed, corrupting the removed
	// entries, hence we must check the iCount value each time round the loop.	
	TInt i=-1;
	while(++i<iCount)
		{
		SDObjectIxRec* pS=iObjects+i;
		DObject *pO=pS->obj;
		if (pO)
			{
			--iActiveCount;
			pS->obj=NULL;	// invalidate entry after closing it
			pO->Close(iPtr);
			}
		}
	SDObjectIxRec* pR=iObjects;
	iObjects=NULL;
	delete pR;
	}


/**	Adds a kernel object to an index and return a handle.

	@param	aObj	Pointer to the object to add
	@param	aHandle	Place to write returned handle to
	
	@return	KErrNone, if operation successful;
	        KErrNoMemory, if there was insufficient memory to expand the array.

    @pre    Calling thread must be in a critical section.
    @pre    No fast mutex can be held.
	@pre	Call in a thread context.
	@pre	DObject::HandleMutex held
 */
TInt DObjectIx::Add(DObject* anObj, TInt& aHandle)
	{
	//Check preconditions(debug build only)
	__ASSERT_CRITICAL;
	__ASSERT_NO_FAST_MUTEX;

	TInt index=iFree; //iFree contains the index of the first empty slot or -1 if there is no such.
	if (index<0) //Check if the free list is empty
		{
		// The free list is empty, so more slots must be allocated.
		if (iCount==KObjectIxMaxHandles)
 			return KErrNoMemory;
		
		//Those are internal checking of the object consistency
		__ASSERT_DEBUG(iAllocated==iCount,Panic(EObjInconsistent));
		__ASSERT_DEBUG(iAllocated==iActiveCount,Panic(EObjInconsistent));
		
		//Double allocated memory
		TInt newAlloc=iAllocated ? 2*iAllocated : KObjectIxGranularity;
		if(newAlloc>KObjectIxMaxHandles)
 			newAlloc=KObjectIxMaxHandles;
		TInt r=Kern::SafeReAlloc((TAny*&)iObjects,iAllocated*sizeof(SDObjectIxRec),newAlloc*sizeof(SDObjectIxRec));
		if (r)
			return r;
		//NOTE: There is no need to initialize newly allocated memory (e.g. to zero iObjects) as it all goes
		//beyond iCount and will be not considered when search in At(...) or operator[]() methods. 
		//As the free list is initially ordered, each slot goes through the states as follows:
		// - Created as the part of the free list beyond iCount. - uninitialized and not searched in any method.
		// - In use  - initialized.
		// - The part of the free list within iCount - initialized to zero.
		//Also bear in mind that UpdateState() does not reorder free list beyond iCount but keeps it preserverd.

		iAllocated=newAlloc;		   //Update the number of allocated slots
		iUpdateDisabled = iAllocated/2;//The number of Remove() calls before the object update(free list,iCount,..)

		//Connect all newly allocated slots into the list and set 'index' to point to the first one.
		SDObjectIxRec* pA = (SDObjectIxRec*)iObjects;
		index=newAlloc-1;
		pA[index].nextEmpty = -1;
		while (iCount <= --index) 
			pA[index].nextEmpty=index+1;
		index++;
		}

	//At this point, 'index' variable points to the slot that will be used for the new entry.
	//It also represents the first element in the list of empty slots.

	SDObjectIxRec *pS=iObjects+index; // pS points to the object that will be used for the new entry.
	iFree=pS->nextEmpty;			  // Update iFree to point to the next empty slot. 
	
	NKern::LockSystem();

	//Initialize data of the new element of the array.
	pS->obj=anObj;
	pS->str.uniqueID=(TUint16)anObj->UniqueID();
	pS->str.instance=(TUint16)instanceLimit(iNextInstance);
	
	iNextInstance++;
	
	if (index>=iCount)	//Update iCount to points to the slot after the last in use.
		iCount=index+1;
	
	++iActiveCount;

	NKern::UnlockSystem();

	//Internal checking of the object consistency
	__ASSERT_DEBUG( (iFree==-1 && iAllocated==iCount && iAllocated==iActiveCount)
				  ||(iFree!=-1 && iAllocated>iActiveCount),Panic(EObjInconsistent));
	
	aHandle=makeHandle(index,pS->str.instance);
	return KErrNone;
  }


/**	Removes a kernel object from an index by handle.

	Returns a pointer to the object removed and the parameter to pass to Close().

	@param	aHandle	Handle to object to be removed.
	@param	aObj	Place to write pointer to the object to add.
	@param	aPtr	Place to write parameter to be passed to Close().
	
	@return	KErrNone, if operation successful;
	        KErrBadHandle, if the supplied handle was invalid.

    @pre    Calling thread must be in a critical section.
    @pre    No fast mutex can be held.
	@pre	Call in a thread context.
	@pre	DObject::HandleMutex held.
 */
TInt DObjectIx::Remove(TInt aHandle, DObject*& aObj, TAny*& aPtr)
	{
	//Check preconditions(debug build only)
	__ASSERT_CRITICAL;
	__ASSERT_NO_FAST_MUTEX;

	TInt i=index(aHandle);
	
	if (i >= iCount)
		return KErrBadHandle;

	SDObjectIxRec* pR=iObjects+i;
	DObject *pO=pR->obj;
	if (!pO || pR->str.instance!=instance(aHandle) || pR->str.uniqueID!=pO->UniqueID())
		return KErrBadHandle;

	NKern::LockSystem();
	pR->obj=NULL;
	--iActiveCount;
	NKern::UnlockSystem();
	
	if(iActiveCount)
		{	
		// Add the entry onto the free list
		pR->nextEmpty=iFree;
		iFree=i;

		if(iUpdateDisabled) 
			iUpdateDisabled--; //Count down till state update is enabled again.

		if (									 //Update the states(HWM, resort free list & memory shrink) if:
			(!iUpdateDisabled) &&				 //There were a number of Remove() calls since the last ReAlloc
			(iAllocated>=2*KObjectIxGranularity))//Allocated memory is above the limit.
			{
			UpdateState();
			iUpdateDisabled = iAllocated/2;//The number of Remove() calls before the next update comes.
			}
		}
	else
		{
		//There is no any CObject left. Reset the object to initial state (except iNextInstance)
		delete iObjects;
		iObjects=NULL;
		iAllocated=0;
		iCount=0;
		iFree=-1;		  //Empty free list
		}

	//This is internal checking of the object consistency
	__ASSERT_DEBUG( (iFree==-1 && iAllocated==iCount && iAllocated==iActiveCount)
				  ||(iFree!=-1 && iAllocated>iActiveCount),Panic(EObjInconsistent));
	
	aObj=pO;
	aPtr=iPtr;
	return KErrNone;
  }


// Private method which:
//1. Reorders free list.
//2. Updates iCount. This is the only place where HWM is decreased, while it can be increased during AddL().
//3. Shrinks the heap memory (pointed by iObjects) if it can be at least halved.
//The function is entered with at least one occupied slot in iObjects array.
//The array is searched from its end. Special care is given to the  case where
//iCount is less then KObjectIxGranularity as the size of the arrey does not go below it.
//Note: HighWaterMark (HWM) is a term used for iCount.
void DObjectIx::UpdateState()
	{
	TBool toShrink = EFalse;
	TBool foundFreeBelowLimit = EFalse;//Used to handle spacial case when HWM is below the minimum alloc. limit
	TInt newHWM = 0;

	//Start from the HWM as all slots beyond are free and sorted already.
	TInt current = iCount;
	TInt prevFreeSlot = iCount == iAllocated ? -1 : iCount;
	while (--current>=0)
		{
		if (iObjects[current].obj)
			{
			//This is the slot with the valid entry. Check if this is the last in the array.
			if(!newHWM)
				{
				//This is the first occupied slot we found => It is new HWM.
				newHWM=current+1;
				if (current < iAllocated/2)
					{
					//At this point we decide to shrink memory.
					toShrink = ETrue;
					//Once we find HWM and decide to shrink, all slots after that point should be removed
					//from the free list as that memory will be freed. The exception is the case when HWM is below
					//the minimum of allocated memory (8 slots as the moment).
					if((current >= KObjectIxGranularity) || (!foundFreeBelowLimit))
						prevFreeSlot = -1; //The next free slot to find will be the last one in the list.
					}
				}
			}
		else
			{
			//This is the free slot.
			if ((!newHWM) && (!foundFreeBelowLimit) &&(current<KObjectIxGranularity))
				{
				//The special case. 
				//We just reached the first free slot below minimum alloc. size and still we found no occupied slots.
				iObjects[current].nextEmpty = -1; //This will be the end of free list.
				foundFreeBelowLimit = ETrue; //Mark that we found the special case
				}
			else
				{
				iObjects[current].nextEmpty = prevFreeSlot;//Link the empty slot in the free list.
				}
			prevFreeSlot = current;
			}
		}

	iCount = newHWM;
	iFree = prevFreeSlot;

	if (toShrink)
		{
		//Do not reallocate less then the initial value.
		TInt newAlloc = Max(newHWM,KObjectIxGranularity);
		//Update member data and re-allocate memory. ReAlloc cannot return NULL as we are asking for less memory.
		Kern::SafeReAlloc((TAny*&)iObjects,iAllocated*sizeof(SDObjectIxRec),newAlloc*sizeof(SDObjectIxRec));
		iAllocated = newAlloc;
		}
	}


/** Counts the number of times an object appears in this index.

	@param	aObject	Object whose occurrences are to be counted.

	@return	Number of times aObject appears in the index.

    @pre    Calling thread must be in a critical section.
    @pre    No fast mutex can be held.
	@pre	Call in a thread context.
	@pre	DObject::HandleMutex held
 */
TInt DObjectIx::Count(DObject* aObject)
	{
	//Check preconditions(debug build only)
	__ASSERT_CRITICAL;
	__ASSERT_NO_FAST_MUTEX;

	TInt c=0;
	if (iCount)
		{
		SDObjectIxRec* pS=iObjects;
		SDObjectIxRec* pE=pS+iCount;
		do
			{
			if (pS->obj==aObject)
				c++;
			} while (++pS<pE);
		}
	return c;
	}


#ifndef __DOBJECT_MACHINE_CODED__
/**	Looks up an object in the index by handle.
	
	The object must be of a specified type (specified by container ID)

	@param	aHandle		Handle to look up.
	@param	aUniqueID	Unique ID (container ID) that object should have.
	
	@return	Pointer to object or NULL if handle invalid.

	@pre	Call in a thread context.
	@pre    System lock must be held.
 */
DObject* DObjectIx::At(TInt aHandle, TInt aUniqueID)
	{
	__ASSERT_SYSTEM_LOCK; //Check preconditions (debug build only)
	TInt i=index(aHandle);
	if (i>=iCount)
		return(NULL);
	SDObjectIxRec *pS=iObjects+i;
	if (pS->str.instance!=instance(aHandle) || pS->str.uniqueID!=aUniqueID)
		return(NULL);
	return(pS->obj);
	}


/**	Looks up an object in the index by handle.

	The object may be of any type.

	@param	aHandle		Handle to look up.
	
	@return	Pointer to object or NULL if handle invalid.

	@pre	Call in a thread context.
	@pre    System lock must be held.
 */
DObject* DObjectIx::At(TInt aHandle)
	{
	__ASSERT_SYSTEM_LOCK; //Check preconditions (debug build only)
	TInt i=index(aHandle);
	if (i>=iCount)
		return NULL;
	SDObjectIxRec *pS=iObjects+i;
	if (pS->str.instance!=instance(aHandle))
		return NULL;
	return pS->obj;
	}
#endif


/**	Looks up an object in the index by object pointer.

	Returns a handle to the object.

	@param	aObj	Pointer to the object to look up.
	
	@return	Handle to object (always >0);
	        KErrNotFound, if object not present in index.

    @pre    Calling thread must be in a critical section.
    @pre    No fast mutex can be held.
	@pre	Call in a thread context.
	@pre	DObject::HandleMutex held.
 */
TInt DObjectIx::At(DObject* aObj)
	{
	//Check preconditions(debug build only)
	__ASSERT_CRITICAL;
	__ASSERT_NO_FAST_MUTEX;

	if (iCount)
		{
		SDObjectIxRec* pS=iObjects;
		SDObjectIxRec* pE=pS+iCount;
		TInt i=0;
		while(pS<pE && pS->obj!=aObj)
			pS++, i++;
		if (pS<pE)
			return(makeHandle(i,pS->str.instance));
		}
	return KErrNotFound;
	}


#ifndef __DOBJECT_MACHINE_CODED__
/** Finds the object at a specific position in the index array.

	@param	aIndex	Index into array.
	
	@return	Pointer to the object at that position (could be NULL).

	@pre	Call in a thread context. 
    @pre    System lock must be held.
 */
DObject* DObjectIx::operator[](TInt aIndex)
	{
	__ASSERT_SYSTEM_LOCK; //Check preconditions (debug build only)
	__ASSERT_ALWAYS(aIndex>=0 && aIndex<iCount,Panic(EArrayIndexOutOfRange));
	return iObjects[aIndex].obj;
	}
#else
GLDEF_C void PanicDObjectIxIndexOutOfRange(void)
	{
	Panic(EArrayIndexOutOfRange);
	}
#endif

TInt DObjectIx::LastHandle()
//
// Return the last valid handle
// Must wait on HandleMutex before calling this.
//
	{
	//Check preconditions(debug build only)
	__ASSERT_CRITICAL;
	__ASSERT_NO_FAST_MUTEX;

	if (iActiveCount)
		{
		SDObjectIxRec* p=iObjects+iCount;
		while(--p>=iObjects && !p->obj) {}
		return makeHandle(p-iObjects,p->str.instance);
		}
	return 0;
	}

//
// Include these in exactly one CPP file somewhere
//

extern TBool gRunningWithOldDefinition = ETrue;

NONSHARABLE_CLASS(DObjectWithPaddingOnly) : public DObject
	{
public:
	DOBJECT_PADDING;
	};

TBool CalculateDObjectSize()
	{
	DObjectWithPaddingOnly* obj = new DObjectWithPaddingOnly;
	if (!obj) return EFalse;
	
	// objectId points to the mem location where iObjectId will be. So if running on a system with the new size DOBject it will always be non-zero (because objectIds are set in the DObject constructor, and are always non-zero), but if running on earlier systems it will be zero because DBase zero-fills the object
	TUint64& objectId = *reinterpret_cast<TUint64*>((TUint8*)&obj->iName + sizeof(HBuf*));
	
	if (objectId != 0)
		{
		//Kern::Printf("Detected MemoryAccess is running with new larger DObject");
		gRunningWithOldDefinition = EFalse;
		}
	else
		{
		//Kern::Printf("Detected MemoryAccess is running with old-format DObject");		
		}
	obj->Close(NULL);
	return ETrue;
	}
