// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"

#ifdef AFX_CORE2_SEG
#pragma code_seg(AFX_CORE2_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

////////////////////////////////////////////////////////////////////////////
// Archive support for polymorphic reading/writing of CObjects

// Note: Starting with MFC 4.0, the file format written/read has been
//  extended to eliminate the previous 32k limit.  Files previously written
//  can still be read by old versions (even 16-bit versions).  In addition,
//  new files, unless they are large enough to take advantage of 32-bit tags,
//  can be read by old versions.

// Pointer mapping constants
#define wNullTag        ((WORD)0)           // special tag indicating NULL ptrs
#define wNewClassTag    ((WORD)0xFFFF)      // special tag indicating new CRuntimeClass
#define wClassTag       ((WORD)0x8000)      // 0x8000 indicates class tag (OR'd)
#define dwBigClassTag   ((DWORD)0x80000000) // 0x8000000 indicates big class tag (OR'd)
#define wBigObjectTag   ((WORD)0x7FFF)      // 0x7FFF indicates DWORD object tag
#define nMaxMapCount    ((DWORD)0x3FFFFFFE) // 0x3FFFFFFE last valid mapCount

// Note: tag value 0x8000 could be used for something in the future, since
//  it is currently an invalid tag (0x8000 means zero wClassTag, but zero
//  index is always reserved for a NULL pointer, and a NULL runtime class
//  does not make any sense).

// This is how the tags have been allocated currently:
//
//  0x0000              represents NULL pointer
//  0x0001 - 0x7FFE     "small" object tags
//  0x7FFF              header for "big" object/class tag
//  0x8000              reserved for future use
//  0x8001 - 0xFFFE     "small" class tag
//  0xFFFF              header for class definition
//
// The special value of 0x7FFF indicates that a DWORD tag follows.  This
// two part "big" tag is used for 32-bit tag values 0x7FFF and above.
// The tag value of 0x7FFF was unused in MFC versions prior to MFC 4.0.

////////////////////////////////////////////////////////////////////////////

void CArchive::CheckCount()
{
	if (m_nMapCount >= nMaxMapCount)
		AfxThrowArchiveException(CArchiveException::badIndex, m_strFileName);
}

void CArchive::WriteObject(const CObject* pOb)
{
	// object can be NULL
	ASSERT(IsStoring());    // proper direction

	DWORD nObIndex;
	ASSERT(sizeof(nObIndex) == 4);
	ASSERT(sizeof(wNullTag) == 2);
	ASSERT(sizeof(wBigObjectTag) == 2);
	ASSERT(sizeof(wNewClassTag) == 2);

	// make sure m_pStoreMap is initialized
	MapObject(NULL);

	if (pOb == NULL)
	{
		// save out null tag to represent NULL pointer
		*this << wNullTag;
	}
	else if ((nObIndex = (DWORD)(*m_pStoreMap)[(void*)pOb]) != 0)
		// assumes initialized to 0 map
	{
		// save out index of already stored object
		if (nObIndex < wBigObjectTag)
			*this << (WORD)nObIndex;
		else
		{
			*this << wBigObjectTag;
			*this << nObIndex;
		}
	}
	else
	{
		// write class of object first
		CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
		WriteClass(pClassRef);

		// enter in stored object table, checking for overflow
		CheckCount();
		(*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;

		// cause the object to serialize itself
		((CObject*)pOb)->Serialize(*this);
	}
}

CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
{
	ASSERT(pClassRefRequested == NULL ||
		AfxIsValidAddress(pClassRefRequested, sizeof(struct CRuntimeClass), FALSE));
	ASSERT(IsLoading());    // proper direction
	ASSERT(wNullTag == 0);
	ASSERT((wClassTag << 16) == dwBigClassTag);
	ASSERT((wNewClassTag & wClassTag) == wClassTag);

	// attempt to load next stream as CRuntimeClass
	UINT nSchema;
	DWORD obTag;
	CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);

	// check to see if tag to already loaded object
	CObject* pOb;
	if (pClassRef == NULL)
	{
		if (obTag > (DWORD)m_pLoadArray->GetUpperBound())
		{
			// tag is too large for the number of objects read so far
			AfxThrowArchiveException(CArchiveException::badIndex,
				m_strFileName);
		}

		pOb = (CObject*)m_pLoadArray->GetAt(obTag);
		if (pOb != NULL && pClassRefRequested != NULL &&
			 !pOb->IsKindOf(pClassRefRequested))
		{
			// loaded an object but of the wrong class
			AfxThrowArchiveException(CArchiveException::badClass,
				m_strFileName);
		}
	}
	else
	{
		// allocate a new object based on the class just acquired
		pOb = pClassRef->CreateObject();
		if (pOb == NULL)
			AfxThrowMemoryException();

		// Add to mapping array BEFORE de-serializing
		CheckCount();
		m_pLoadArray->InsertAt(m_nMapCount++, pOb);

		// Serialize the object with the schema number set in the archive
		UINT nSchemaSave = m_nObjectSchema;
		m_nObjectSchema = nSchema;
		pOb->Serialize(*this);
		m_nObjectSchema = nSchemaSave;
		ASSERT_VALID(pOb);
	}

	return pOb;
}

/////////////////////////////////////////////////////////////////////////////
// advanced versioning and back-pointer support

UINT CArchive::GetObjectSchema()
{
	UINT nResult = m_nObjectSchema;
	m_nObjectSchema = (UINT)-1; // can only be called once per Serialize
	return nResult;
}

void CArchive::MapObject(const CObject* pOb)
{
	if (IsStoring())
	{
		if (m_pStoreMap == NULL)
		{
			// initialize the storage map
			//  (use CMapPtrToPtr because it is used for HANDLE maps too)
			m_pStoreMap = new CMapPtrToPtr(m_nGrowSize);
			m_pStoreMap->InitHashTable(m_nHashSize);
			m_pStoreMap->SetAt(NULL, (void*)(DWORD)wNullTag);
			m_nMapCount = 1;
		}
		if (pOb != NULL)
		{
			CheckCount();
			(*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
		}
	}
	else
	{
		if (m_pLoadArray == NULL)
		{
			// initialize the loaded object pointer array and set special values
			m_pLoadArray = new CPtrArray;
			m_pLoadArray->SetSize(1, m_nGrowSize);
			ASSERT(wNullTag == 0);
			m_pLoadArray->SetAt(wNullTag, NULL);
			m_nMapCount = 1;
		}
		if (pOb != NULL)
		{
			CheckCount();
			m_pLoadArray->InsertAt(m_nMapCount++, (void*)pOb);
		}
	}
}

void CArchive::WriteClass(const CRuntimeClass* pClassRef)
{
	ASSERT(pClassRef != NULL);
	ASSERT(IsStoring());    // proper direction

	if (pClassRef->m_wSchema == 0xFFFF)
	{
		TRACE1("Warning: Cannot call WriteClass/WriteObject for %hs.\n",
			pClassRef->m_lpszClassName);
		AfxThrowNotSupportedException();
	}

	// make sure m_pStoreMap is initialized
	MapObject(NULL);

	// write out class id of pOb, with high bit set to indicate
	// new object follows

	// ASSUME: initialized to 0 map
	DWORD nClassIndex;
	if ((nClassIndex = (DWORD)(*m_pStoreMap)[(void*)pClassRef]) != 0)
	{
		// previously seen class, write out the index tagged by high bit
		if (nClassIndex < wBigObjectTag)
			*this << (WORD)(wClassTag | nClassIndex);
		else
		{
			*this << wBigObjectTag;
			*this << (dwBigClassTag | nClassIndex);
		}
	}
	else
	{
		// store new class
		*this << wNewClassTag;
		pClassRef->Store(*this);

		// store new class reference in map, checking for overflow
		CheckCount();
		(*m_pStoreMap)[(void*)pClassRef] = (void*)m_nMapCount++;
	}
}

CRuntimeClass* CArchive::ReadClass(const CRuntimeClass* pClassRefRequested,
	UINT* pSchema, DWORD* pObTag)
{
	ASSERT(pClassRefRequested == NULL ||
		AfxIsValidAddress(pClassRefRequested, sizeof(struct CRuntimeClass), FALSE));
	ASSERT(IsLoading());    // proper direction

	if (pClassRefRequested != NULL && pClassRefRequested->m_wSchema == 0xFFFF)
	{
		TRACE1("Warning: Cannot call ReadClass/ReadObject for %hs.\n",
			pClassRefRequested->m_lpszClassName);
		AfxThrowNotSupportedException();
	}

	// make sure m_pLoadArray is initialized
	MapObject(NULL);

	// read object tag - if prefixed by wBigObjectTag then DWORD tag follows
	DWORD obTag;
	WORD wTag;
	*this >> wTag;
	if (wTag == wBigObjectTag)
		*this >> obTag;
	else
		obTag = ((wTag & wClassTag) << 16) | (wTag & ~wClassTag);

	// check for object tag (throw exception if expecting class tag)
	if (!(obTag & dwBigClassTag))
	{
		if (pObTag == NULL)
			AfxThrowArchiveException(CArchiveException::badIndex, m_strFileName);

		*pObTag = obTag;
		return NULL;
	}

	CRuntimeClass* pClassRef;
	UINT nSchema;
	if (wTag == wNewClassTag)
	{
		// new object follows a new class id
		if ((pClassRef = CRuntimeClass::Load(*this, &nSchema)) == NULL)
			AfxThrowArchiveException(CArchiveException::badClass, m_strFileName);

		// check nSchema against the expected schema
		if ((pClassRef->m_wSchema & ~VERSIONABLE_SCHEMA) != nSchema)
		{
			if (!(pClassRef->m_wSchema & VERSIONABLE_SCHEMA))
			{
				// schema doesn't match and not marked as VERSIONABLE_SCHEMA
				AfxThrowArchiveException(CArchiveException::badSchema,
					m_strFileName);
			}
			else
			{
				// they differ -- store the schema for later retrieval
				if (m_pSchemaMap == NULL)
					m_pSchemaMap = new CMapPtrToPtr;
				ASSERT_VALID(m_pSchemaMap);
				m_pSchemaMap->SetAt(pClassRef, (void*)nSchema);
			}
		}
		CheckCount();
		m_pLoadArray->InsertAt(m_nMapCount++, pClassRef);
	}
	else
	{
		// existing class index in obTag followed by new object
		DWORD nClassIndex = (obTag & ~dwBigClassTag);
		if (nClassIndex == 0 || nClassIndex > (DWORD)m_pLoadArray->GetUpperBound())
			AfxThrowArchiveException(CArchiveException::badIndex,
				m_strFileName);

		pClassRef = (CRuntimeClass*)m_pLoadArray->GetAt(nClassIndex);
		ASSERT(pClassRef != NULL);

		// determine schema stored against objects of this type
		void* pTemp;
		if (m_pSchemaMap != NULL && m_pSchemaMap->Lookup(pClassRef, pTemp))
			nSchema = (UINT)pTemp;
		else
			nSchema = pClassRef->m_wSchema & ~VERSIONABLE_SCHEMA;
	}

	// check for correct derivation
	if (pClassRefRequested != NULL &&
		!pClassRef->IsDerivedFrom(pClassRefRequested))
	{
		AfxThrowArchiveException(CArchiveException::badClass, m_strFileName);
	}

	// store nSchema for later examination
	if (pSchema != NULL)
		*pSchema = nSchema;
	else
		m_nObjectSchema = nSchema;

	// store obTag for later examination
	if (pObTag != NULL)
		*pObTag = obTag;

	// return the resulting CRuntimeClass*
	return pClassRef;
}

void CArchive::SerializeClass(const CRuntimeClass* pClassRef)
{
	if (IsStoring())
		WriteClass(pClassRef);
	else
		ReadClass(pClassRef);
}

////////////////////////////////////////////////////////////////////////////
