/* $Id$ */

/**
 * \file 
 * $Revision$
 * $Date$
 * 
 * Copyright (C) 2012 The University of Sydney, Australia
 *
 * This file is part of GPlates.
 *
 * GPlates is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2, as published by
 * the Free Software Foundation.
 *
 * GPlates 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 Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <boost/checked_delete.hpp>
#include <boost/foreach.hpp>
#include <boost/utility/in_place_factory.hpp>
#include <QDebug>

#include "Scribe.h"


// We give names that are unlikely to conflict with names used by scribe clients.
// So we prepend with "scribe_".
const GPlatesScribe::ObjectTag GPlatesScribe::Scribe::POINTS_TO_OBJECT_TAG("scribe_points_to_object");
const GPlatesScribe::ObjectTag GPlatesScribe::Scribe::POINTS_TO_CLASS_TAG("scribe_points_to_class");


GPlatesScribe::Scribe::Scribe() :
	d_is_saving(true),
	d_transcription(Transcription::create()),
	d_transcription_context(d_transcription, d_is_saving),
	d_transcribe_result(TRANSCRIBE_SUCCESS),
	// This is only here to force 'ScribeAccess.o' object file to get referenced and included by linker...
	d_exported_registered_classes(Access::EXPORT_REGISTERED_CLASSES)
{
}


GPlatesScribe::Scribe::Scribe(
		const Transcription::non_null_ptr_type &transcription) :
	d_is_saving(false),
	d_transcription(transcription),
	d_transcription_context(transcription, d_is_saving),
	d_transcribe_result(TRANSCRIBE_SUCCESS),
	// This is only here to force 'ScribeAccess.o' object file to get referenced and included by linker...
	d_exported_registered_classes(Access::EXPORT_REGISTERED_CLASSES)
{
	// Make sure the transcription is complete before we start loading from it.
	GPlatesGlobal::Assert<Exceptions::TranscriptionIncomplete>(
			d_transcription->is_complete(NULL_POINTER_OBJECT_ID, false/*emit_warnings*/),
			GPLATES_ASSERTION_SOURCE);
}


bool
GPlatesScribe::Scribe::is_in_transcription(
		const ObjectTag &object_tag) const
{
	return static_cast<bool>(d_transcription_context.is_in_transcription(object_tag));
}


bool
GPlatesScribe::Scribe::is_transcription_complete(
		bool emit_warnings) const
{
	bool transcription_complete = true;

	// If any objects have not been initialised then return false.
	BOOST_FOREACH(ObjectInfo *object_info, d_object_infos)
	{
		// Ignore object slots that were never used or were untracked.
		//
		// The object id slot might not be used, for example, if no attempt was even made to load an
		// object (eg, due to archive generated by a future GPlates).
		if (object_info == NULL)
		{
			continue;
		}

		// We don't need to check untracked objects that are successfully transcribed because if they
		// are not (post) initialised at the time they are untracked (right after they are transcribed)
		// then they will report failure at that time.
		// We don't need to check discarded objects (objects that fail to load and hence are
		// immediately untracked) because they were discarded and so should not be considered.
		//
		// However we do need to check tracked objects (done below) because they can be un-initialised
		// when they are transcribed (they can wait for the pointed-to object to get transcribed later).
		// But that may never happen - which is why we are doing this check in the first place.
		//
		// Untracked and discarded objects are not pre-initialised or post-initialised.
		//
		// Tracked objects that are pre-initialised but not post-initialised are pointers that
		// never were initialised (or were initialised but then were un-initialised if their
		// pointed-to object was subsequently discarded).
		if (object_info->is_object_pre_initialised &&
			!object_info->is_object_post_initialised)
		{
			transcription_complete = false;

			if (emit_warnings)
			{
				// If the object has not been transcribed/initialised then it's possible that
				// it has no class id or info yet.
				// If it does then we'll log a more specialised warning that gives the object type.
				if (object_info->class_id &&
					object_info->class_id.get() < d_class_infos.size() &&
					d_class_infos[object_info->class_id.get()]->initialised)
				{
					ClassInfo &class_info = *d_class_infos[object_info->class_id.get()];

					qWarning() << "Transcribed object of type '"
							<< class_info.object_type_info.get()->name()
							<< "' was not successfully initialised.";
				}
				else
				{
					qWarning() << "Transcribed object of unregistered type was not successfully initialised.";
				}

				// Output the call stack trace at the time the object was transcribed (applies to pointers
				// that are transcribed but not initialised because pointed-to object was never transcribed).
				if (object_info->uninitialised_transcribe_call_stack)
				{
					qWarning() << "Call stack trace:";

					transcribe_call_stack_type::const_iterator trace_iter =
							object_info->uninitialised_transcribe_call_stack->begin();
					transcribe_call_stack_type::const_iterator trace_end =
							object_info->uninitialised_transcribe_call_stack->end();
					for ( ; trace_iter != trace_end; ++trace_iter)
					{
						const GPlatesUtils::CallStack::Trace &trace = *trace_iter;

						qWarning()
							<< '('
							<< trace.get_filename()
							<< ", "
							<< trace.get_line_num()
							<< ')';
					}
				}
			}

			// Continue on to the next object so that we log warnings for all un-initialised objects.
		}
	}

	// If there have been no errors so far then go ahead and test the Transcription itself for completeness.
	// We don't test it if there have already been errors because otherwise we'll get more errors
	// that essentially say the same thing (but will be more cryptic than the above error messages).
	if (transcription_complete)
	{
		// Check that the transcription itself is complete (eg, no object id references to missing objects).
		// We only do this if saving. On the loading path this is checked in the constructor.
		if (is_saving())
		{
			if (!d_transcription->is_complete(NULL_POINTER_OBJECT_ID, emit_warnings))
			{
				transcription_complete = false;
			}
		}
	}

	return transcription_complete;
}


void
GPlatesScribe::Scribe::pre_transcribe(
		object_id_type object_id,
		class_id_type class_id,
		const object_address_type &object_address)
{
	// Should not be possible to get a NULL pointer here.
	// A transcribed object (including a pointer object) always has a non-NULL object address.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_id != NULL_POINTER_OBJECT_ID,
			GPLATES_ASSERTION_SOURCE,
			"Encountered NULL pointer object id, but object should have a non-NULL address.");

	// The class type should have been registered by now.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			get_class_info(class_id).initialised,
			GPLATES_ASSERTION_SOURCE,
			"Streaming an object before its class type has been registered.");

	//
	// Initialise object info
	//

	// Get the object info.
	ObjectInfo &object_info = get_object_info(object_id);

	// Object should not already have been transcribed.
	if (object_info.is_object_pre_initialised)
	{
		ClassInfo &class_info = get_class_info(class_id);

		// All class info's should have an object type info.
		GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
				class_info.object_type_info,
				GPLATES_ASSERTION_SOURCE,
				"Pre-transcribing an object before its object type info is available.");

		// Throw exception if the object has already been transcribed.
		//
		// If this assertion is triggered then it means:
		//   * Scribe client has called 'transcribe' more than once for the same object address (with tracking enabled), or
		//   * Scribe client has transcribed an owning pointer through a non-polymorphic base class pointer and
		//     then transcribed the same object through a derived class pointer, or
		//   * Scribe client has created an island of objects that cyclically own each other (memory leak).
		//
		// The following example can trigger this assertion:
		//
		//   A a;
		//   scribe.transcribe(TRANSCRIBE_SOURCE, a, "a", GPlatesScribe::TRACK);
		//   d_array[0] = a;
		//   scribe.transcribe(TRANSCRIBE_SOURCE, a, "a", GPlatesScribe::TRACK); // Error: have already transcribed to object address "&a".
		//   d_array[1] = a;
		//
		// ...which can be solved by...
		//
		//   scribe.transcribe(TRANSCRIBE_SOURCE, d_array[0], "a", GPlatesScribe::TRACK);
		//   scribe.transcribe(TRANSCRIBE_SOURCE, d_array[1], "a", GPlatesScribe::TRACK); // Is fine since &d_array[1] != &d_array[0]
		//
		// ...or...
		//
		//   A a;
		//   scribe.transcribe(TRANSCRIBE_SOURCE, a, "a", GPlatesScribe::TRACK);
		//   d_array[0] = a;
		//   scribe.relocated(TRANSCRIBE_SOURCE, d_array[0], a); // Now registers &d_array[0] as the object address.
		//   scribe.transcribe(TRANSCRIBE_SOURCE, a, "a", GPlatesScribe::TRACK); // Is fine since &a != &d_array[0]
		//   d_array[1] = a;
		//   scribe.relocated(TRANSCRIBE_SOURCE, d_array[1], a); // Now registers &d_array[1] as the object address.
		//
		GPlatesGlobal::Assert<Exceptions::AlreadyTranscribedObject>(
				false,
				GPLATES_ASSERTION_SOURCE,
				*class_info.object_type_info.get(),
				is_saving());
	}

	// The object is currently being transcribed.
	object_info.is_object_pre_initialised = true;

	// Set the object's address - even before we've streamed the object.
	// Note that this address is valid in both the save and load paths.
	object_info.object_address = object_address.address;

	// Set the object's class id.
	object_info.class_id = class_id;

	if (is_loading())
	{
		// Map the tracked load object address to its object id.
		// This has already been done in the save path in 'transcribe_object_id()'.
		//
		// Note that we do this even for untracked objects because they are still tracked
		// while they're being transcribed (to facilitate child object relocations).
		// Once they have finished being transcribed they will get untracked.
		map_tracked_load_object_address_to_object_id(object_address, object_id);
	}

	// We've started transcribing a new object.
	push_transcribed_object(object_id);
}


void
GPlatesScribe::Scribe::post_transcribe(
		object_id_type object_id,
		unsigned int options,
		bool discard,
		bool is_object_initialised)
{
	ObjectInfo &object_info = get_object_info(object_id);

	// Non-pointer objects will have just been transcribed and so the object itself will be initialised.
	// However pointer objects may not have been resolved (to point to pointed-to object) if the
	// pointed-to object has not yet been transcribed.
	object_info.is_object_post_initialised = is_object_initialised;

	// If the object is not initialised (a pointer where pointed-to object has not yet been transcribed)
	// then save the current call stack in case the pointer never gets initialised and we need to
	// print an error message showing where (in the source code) it was transcribed.
	if (!is_object_initialised)
	{
		object_info.uninitialised_transcribe_call_stack = boost::in_place(
				GPlatesUtils::CallStack::instance().call_stack_begin(),
				GPlatesUtils::CallStack::instance().call_stack_end());
	}

	// Note that untracked objects include those that failed to be transcribed
	// (in addition to those explicitly specified as untracked by scribe clients).
	if (discard ||
		((options & TRACK) == 0))
	{
		// The object is not being tracked so we need to un-track it and all its child-objects.
		// We have not disabled tracking of child-objects until now in order that they can
		// get relocated as they are transcribed - this can happen when transcribing with non-default
		// object constructors whereby the transcribed constructor parameters need to be relocated
		// inside the object (hence becoming sub-objects) once the object constructor returns -
		// it can also happen when transcribing objects like std::vector since the transcribed
		// vector elements still need to get relocated into the vector.
		unmap_tracked_object_address_to_object_id(object_id, discard);
	}
	else
	{
		// Initialise all pointers (referencing our object) with our object's address.
		// In the save path this just records the pointers as initialised.
		//
		// Note: We don't do this for *untracked* (or discarded) objects because pointers to
		// untracked objects are not allowed - those pointers will never get initialised.
		resolve_pointers_referencing_object(object_id);
	}

	// We've finished transcribing the current object.
	pop_transcribed_object(object_id);
}


bool
GPlatesScribe::Scribe::transcribe_object_id(
		const object_address_type &save_object_address,
		const ObjectTag &object_tag,
		boost::optional<object_id_type &> return_object_id)
{
	object_id_type object_id;

	if (is_saving())
	{
		// Handle NULL pointers (in case transcribing an object through a pointer instead of directly).
		if (save_object_address.address == NULL)
		{
			// There's no pointed-to object, so write out a special object id to signify this.
			object_id = NULL_POINTER_OBJECT_ID;
		}
		else // non-null object address...
		{
			// The integer identifier of the object being transcribed.
			object_id = get_or_create_save_object_id_and_map_tracked_object_address(save_object_address);
		}
	}

	if (!d_transcription_context.transcribe_object_id(object_id, object_tag))
	{
		// Couldn't find 'object_tag' (in the load path) within parent object scope.

		// Record the reason for transcribe failure.
		set_transcribe_result(TRANSCRIBE_SOURCE, TRANSCRIBE_INCOMPATIBLE);

		return false;
	}

	if (is_loading())
	{
		if (object_id != NULL_POINTER_OBJECT_ID)
		{
			// Create an ObjectInfo for the object id.
			// This has already been done in the save path in this method.
			get_or_create_load_object_info(object_id);

			// NOTE: We wait until transcribing has started on the object before we map its tracked
			// object address to object id. We can't do it here because the address is not always
			// available. For example, when a non-owning pointer is transcribing a pointed-to
			// object id - if the pointed-to object is yet to be transcribed then its address is
			// not yet available.
		}
	}

	// Return to caller if requested.
	if (return_object_id)
	{
		return_object_id.get() = object_id;
	}

	set_transcribe_result(TRANSCRIBE_SOURCE, TRANSCRIBE_SUCCESS);

	return true;
}


bool
GPlatesScribe::Scribe::transcribe_class_name(
		const std::type_info *save_class_type_info,
		boost::optional<const ExportClassType &> &export_class_type)
{
	std::string class_name;

	if (is_saving())
	{
		GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
				save_class_type_info,
				GPLATES_ASSERTION_SOURCE,
				"Expecting non-null class type info in save path.");

		// Find the export registered class type for the pointed-to object.
		export_class_type = ExportRegistry::instance().get_class_type(*save_class_type_info);

		// Throw exception if the object's type has not been export registered.
		//
		// If this assertion is triggered then it means:
		//   * The object's derived type was not export registered (see 'ScribeExportRegistration.h').
		//
		GPlatesGlobal::Assert<Exceptions::UnregisteredClassType>(
				export_class_type,
				GPLATES_ASSERTION_SOURCE,
				*save_class_type_info);

		class_name = export_class_type->type_id_name;
	}

	// Transcribe the class name as a string object.
	//
	// Note: This is one of the only times when an object is transcribed that does not actually
	// belong to the scribe client.
	if (!transcribe(TRANSCRIBE_SOURCE, class_name, POINTS_TO_CLASS_TAG))
	{
		// Couldn't find 'object_tag' (in the load path) within parent object scope.

		// Record the reason for transcribe failure.
		// We don't really need to do this since the above 'transcribe()' call has done this.
		set_transcribe_result(TRANSCRIBE_SOURCE, TRANSCRIBE_INCOMPATIBLE);

		return false;
	}

	if (is_loading())
	{
		// Find the export registered class type associated with the class name.
		export_class_type = ExportRegistry::instance().get_class_type(class_name);

		// If the class name has not been export registered then it means either:
		//   * the archive was created by a future GPlates with a class name we don't know about, or
		//   * the archive was created by an old GPlates with a class name we have since removed.
		//
		if (!export_class_type)
		{
			// Record the reason for transcribe failure.
			set_transcribe_result(TRANSCRIBE_SOURCE, TRANSCRIBE_UNKNOWN_TYPE);

			return false;
		}
	}

	set_transcribe_result(TRANSCRIBE_SOURCE, TRANSCRIBE_SUCCESS);

	return true;
}


void
GPlatesScribe::Scribe::relocated_address(
		object_id_type transcribed_object_id,
		const object_address_type &transcribed_object_address,
		const object_address_type &relocated_object_address,
		std::size_t relocation_pointer_offset,
		bool is_relocation_pointer_offset_positive)
{
	GPlatesGlobal::Assert<Exceptions::ScribeUserError>(
			is_loading(),
			GPLATES_ASSERTION_SOURCE,
			"Can only relocate a transcribed object when loading an archive (not saving).");

	// Get the transcribed object info.
	ObjectInfo &transcribed_object_info = get_object_info(transcribed_object_id);

	// Transcribed object should have been, or should be in the process of being, transcribed.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			transcribed_object_info.is_object_pre_initialised &&
					transcribed_object_info.object_address,
			GPLATES_ASSERTION_SOURCE,
			"Object, being relocated, has not been transcribed.");

	// Throw exception if the transcribed object already has a reference bound to it
	// (or an untracked pointer bound to it).
	//
	// If this assertion is triggered then it means:
	//   * There's a reference (or untracked pointer) that has been bound to the transcribed object
	//     and hence cannot be relocated (because references cannot be re-bound to relocated object
	//     and untracked pointers cannot be updated to point to relocated object).
	//
	// ...to fix this try transcribing the object in a way that does not require you to move it
	// (relocate it) after it has been transcribed. Otherwise try changing the reference
	// (or untracked pointer) to a tracked pointer (thus enabling it to be re-bound/updated once
	// the object has moved/relocated).
	//
	GPlatesGlobal::Assert<Exceptions::RelocatedObjectBoundToAReferenceOrUntrackedPointer>(
			!transcribed_object_info.is_load_object_bound_to_a_reference_or_untracked_pointer,
			GPLATES_ASSERTION_SOURCE);

	// Iterate over the transcribed sub-objects of the current transcribed object.
	object_ids_list_type::const_iterator transcribed_sub_objects_iter =
			transcribed_object_info.sub_objects.begin();
	object_ids_list_type::const_iterator transcribed_sub_objects_end =
			transcribed_object_info.sub_objects.end();
	for ( ; transcribed_sub_objects_iter != transcribed_sub_objects_end; ++transcribed_sub_objects_iter)
	{
		const object_id_type transcribed_sub_object_id = *transcribed_sub_objects_iter;

		// The object address of the transcribed sub-object.
		const boost::optional<object_address_type> transcribed_sub_object_address =
				find_object_address(transcribed_sub_object_id);

		// Throw exception if the transcribed sub-object cannot be found.
		//
		// If this assertion is triggered then it means:
		//   * Object tracking was turned off (ie, we can't find the sub-object being relocated).
		//
		GPlatesGlobal::Assert<Exceptions::RelocatedUntrackedObject>(
				transcribed_sub_object_address,
				GPLATES_ASSERTION_SOURCE);

		// The sub-object is contained *inline* within the parent object so it will be
		// directly affected by the relocation.
		// When the sub-object is a pointer to another object then, since the pointed-to object is
		// not contained *inline*, the pointed-to object will not, in turn, be a sub-object of the pointer.
		// Note however that the parent object's 'relocated' handler can still decide that there
		// are *indirect* affects of the relocation - see comment in "Transcribe.h" for 'relocated()'.

		// Calculate the address of the relocated sub-object as a positive or negative offset
		// from the transcribed sub-object.
		object_address_type relocated_sub_object_address(transcribed_sub_object_address.get());
		relocated_sub_object_address.address = is_relocation_pointer_offset_positive
				? reinterpret_cast<void *>(
						reinterpret_cast<std::size_t>(transcribed_sub_object_address->address) +
								relocation_pointer_offset)
				: reinterpret_cast<void *>(
						reinterpret_cast<std::size_t>(transcribed_sub_object_address->address) -
								relocation_pointer_offset);

		// Recursively handle relocation for the sub-objects of the current inline object.
		relocated_address(
				transcribed_sub_object_id,
				transcribed_sub_object_address.get(),
				relocated_sub_object_address,
				relocation_pointer_offset,
				is_relocation_pointer_offset_positive);
	}

	// Change the transcribed object's address to the relocated address.
	d_tracked_object_address_to_id_map.erase(transcribed_object_address);
	transcribed_object_info.object_address = relocated_object_address.address;
	d_tracked_object_address_to_id_map[relocated_object_address] = transcribed_object_id;

	// Adjust any pointers referencing the transcribed object to point to its new (relocated) memory address.
	resolve_pointers_referencing_object(transcribed_object_id);

	// The class info of the transcribed object.
	ClassInfo &transcribed_class_info = get_class_info_from_object(transcribed_object_id);

	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			transcribed_class_info.initialised,
			GPLATES_ASSERTION_SOURCE,
			"Relocated object does not have a registered class type.");

	// Do any object-specific relocation handling.
	// This, in turn, calls the non-member 'relocated()' specialisation or overload, if any, (see "Transcribe.h").
	transcribed_class_info.relocated_handler.get()->relocated(
			*this,
			relocated_object_address.address,
			transcribed_object_address.address);
}


void
GPlatesScribe::Scribe::set_transcribe_result(
		const GPlatesUtils::CallStack::Trace &transcribe_source,
		TranscribeResult transcribe_result)
{
	if (transcribe_result == TRANSCRIBE_SUCCESS)
	{
		// If we are transitioning to success from failure then clear the call stack trace.
		if (d_transcribe_result != TRANSCRIBE_SUCCESS)
		{
			d_transcribe_incompatible_call_stack.clear();
		}
	}
	else
	{
		// If we are transitioning from success to failure then record the current call stack trace.
		if (d_transcribe_result == TRANSCRIBE_SUCCESS)
		{
			// Track the file/line of the call site.
			GPlatesUtils::CallStackTracker call_stack_tracker(transcribe_source);

			d_transcribe_incompatible_call_stack.assign(
					GPlatesUtils::CallStack::instance().call_stack_begin(),
					GPlatesUtils::CallStack::instance().call_stack_end());
		}
	}

	d_transcribe_result = transcribe_result;
}


GPlatesScribe::Scribe::object_id_type
GPlatesScribe::Scribe::get_or_create_save_object_id_and_map_tracked_object_address(
		const object_address_type &object_address)
{
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			is_saving(),
			GPLATES_ASSERTION_SOURCE,
			"Attempted to get or create save object id when loading an archive.");

	// Attempt to insert the object address into the tracked-object-address-to-id map.
	const std::pair<tracked_object_address_to_id_map_type::iterator, bool> object_address_inserted =
			d_tracked_object_address_to_id_map.insert(
					tracked_object_address_to_id_map_type::value_type(
							object_address,
							0/* dummy object id */));

	object_id_type object_id;

	if (object_address_inserted.second)
	{
		//
		// Insertion was successful so create a new object info.
		//

		// Get the next save object id.
		object_id = d_transcription_context.allocate_save_object_id();

		// Create an ObjectInfo from the pool.
		ObjectInfo *object_info = d_object_info_pool.construct(object_id);

		// Make room in the vector - this resize should always happen in this save path.
		if (object_id >= boost::numeric_cast<object_id_type>(d_object_infos.size()))
		{
			d_object_infos.resize(object_id + 1, NULL);
		}

		// Add to the list.
		d_object_infos[object_id] = object_info;

		// Note: In this save path there should be no NULLs in 'd_object_infos' except at
		// 'NULL_POINTER_OBJECT_ID'.
		// On the load path there can be NULLs though (due to not transcribing objects from an
		// archive generated by a future GPlates because some future objects were not recognised).

		// Assign new object id to the inserted map entry.
		object_address_inserted.first->second = object_id;
	}
	else // not inserted...
	{
		// The object address already exists in the map (insertion failed) so use its object id.
		object_id = object_address_inserted.first->second;
	}

	return object_id;
}


void
GPlatesScribe::Scribe::get_or_create_load_object_info(
		object_id_type object_id)
{
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			is_loading(),
			GPLATES_ASSERTION_SOURCE,
			"Attempted to get or create load object id when saving an archive.");

	// Make room in the vector if necessary.
	if (object_id >= boost::numeric_cast<object_id_type>(d_object_infos.size()))
	{
		d_object_infos.resize(object_id + 1, NULL);
	}

	// Note: On this load path there can be extra NULLs (due to not loading some objects from an
	// archive generated by a future GPlates because some future objects were not recognised).

	// Create ObjectInfo for 'object_id' if hasn't already been created.
	ObjectInfo *object_info = d_object_infos[object_id];
	if (object_info == NULL)
	{
		// Create an ObjectInfo from the pool.
		object_info = d_object_info_pool.construct(object_id);

		// Add it to the list.
		d_object_infos[object_id] = object_info;
	}
}


void
GPlatesScribe::Scribe::map_tracked_load_object_address_to_object_id(
		const object_address_type &object_address,
		object_id_type object_id)
{
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			is_loading(),
			GPLATES_ASSERTION_SOURCE,
			"Attempted to map tracked load object id when saving an archive.");

	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_address.address,
			GPLATES_ASSERTION_SOURCE,
			"Attempted to map a NULL object address to an object id.");

	// Insert the object address into the tracked-object-address-to-id map.
	d_tracked_object_address_to_id_map[object_address] = object_id;
}


void
GPlatesScribe::Scribe::unmap_tracked_object_address_to_object_id(
		object_id_type object_id,
		bool discard)
{
	ObjectInfo &object_info = get_object_info(object_id);

	//
	// Note that we recurse into child objects first since they will remove themselves as
	// children from the current object (so we need to keep current object around for that to happen).
	//

	// Recurse into child-objects...
	//
	// The parent object is either being discarded or it's being untracked (ie, client requested no tracking).
	//
	// Discarding means that the object will probably get destructed and take with it any objects
	// it references with shared pointers. So we must untrack all of these child objects.
	// For example the objects referenced by shared pointers are child objects - they are not
	// sub-objects because they are not inside the memory area of the (parent) object.
	//
	// Untracking means that, while the object may not get destructed immediately (because client
	// is not discarding it), we cannot know how long the object will hang around. For example,
	// the client might copy it (and destroy the original) - but that means any tracked child objects
	// will now be tracking the wrong memory location (eg, moving a std::vector will also move its
	// child objects - the vector elements - to a new location inside the new vector, but the vector
	// did not get relocated because you can't relocate an untracked vector).
	// So, like discarding, we are forced to untrack all child objects.
	//
	// However this brings up the issue of multiple shared pointers referencing an object.
	// What if the first transcribed shared pointer is fine but the second one is discarded ?
	// The second one shouldn't untrack the object (because first one still references it).
	// It turns out that when the second shared pointer was transcribed it didn't need to
	// transcribe the object (because first pointer transcribed it) and hence it did not
	// record the object as a child object and hence it won't get untracked here.
	// But what if the first transcribed shared pointer was discarded and the second one was fine ?
	// When the first one is discarded it gets untracked. When the second one is transcribed
	// there is no knowledge of the first one having been transcribed (its ObjectInfo gets
	// removed - set to NULL) and so it gets transcribed a second time.
	//
	// A similar situation is multiple *untracked* shared pointers referencing an object.
	// The first shared pointer to transcribe the object will immediately untrack the object.
	// When the second shared pointer looks for the object it doesn't find it (since it's untracked)
	// and so it transcribes a new object. Same with the third, etc, shared pointers.
	// This means each shared pointer will have its own copy of the object instead of all sharing one object.
	// This basically means that shared pointers should always be transcribed with tracking enabled
	// (if want multiple pointers to share a single object).
	//
	// Iterate over the child-objects.
	object_ids_list_type::const_iterator child_objects_iter = object_info.child_objects.begin();
	object_ids_list_type::const_iterator child_objects_end = object_info.child_objects.end();
	for ( ; child_objects_iter != child_objects_end; )
	{
		// Get the child-object id.
		const object_id_type child_object_id = *child_objects_iter;

		// Increment iterator before child object removes itself from our list (thus invalidating it).
		++child_objects_iter;

		unmap_tracked_object_address_to_object_id(child_object_id, discard);
	}

	// There should not be any untracked pointers or references referencing this untracked object.
	// And if this untracked object is not being discarded then it can't have any pointers referencing it.
	if (object_info.is_load_object_bound_to_a_reference_or_untracked_pointer ||
		(!discard && !object_info.pointers_referencing_object.empty()))
	{
		ClassInfo &class_info = get_class_info_from_object(object_id);

		// All class info's should have an object type info.
		GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
				class_info.object_type_info,
				GPLATES_ASSERTION_SOURCE,
				"Unmapping a tracked object before its object type info is available.");

		// Throw exception if the object is not tracked and there are pointers/references referencing it.
		//
		// If this assertion is triggered then it means:
		//   * Scribe client has transcribed an *untracked* object and has transcribed
		//     pointers/references to it. To fix this either track the object or avoid
		//     transcribing pointers to it, or
		//   * It's a case of a failed or discarded transcribed object that happens to have
		//     references or *untracked* pointers to it. It's possible that whatever is referencing
		//     it will also get discarded (and hence there will be no problem), but we can't know that
		//     (because references and untracked pointers are not tracked) and so we are forced to
		//     trigger an assertion here.
		//
		// The object is *not* being tracked so it cannot have any pointers/references to it because we
		// cannot assume the object will remain at its current address once we return from transcribing it
		// - by turning off tracking the client is telling us this.
		// If we previously initialised pointers to point to this object and the object was subsequently
		// moved (by the client without relocating) then the pointers would point to the wrong memory location.
		GPlatesGlobal::Assert<Exceptions::UntrackingObjectWithReferences>(
				false,
				GPLATES_ASSERTION_SOURCE,
				*class_info.object_type_info.get());
	}

	// There should not be any un-initialised pointers (unless discarding object).
	// Note that non-pointer objects are always initialised so if the object is un-initialised
	// then it must be a pointer.
	if (!object_info.is_object_post_initialised &&
		!discard)
	{
		ClassInfo &class_info = get_class_info_from_object(object_id);

		// All *pointer* class info's should have a dereference object type info.
		GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
				class_info.dereference_type_info,
				GPLATES_ASSERTION_SOURCE,
				"Unmapping a tracked pointer before its dereference object type info is available.");

		// Throw exception if the pointer is not tracked and not initialised to point to an object yet.
		//
		// If this assertion is triggered then it means:
		//   * Scribe client has transcribed an *untracked* pointer before transcribing
		//     the pointed-to object. To fix this either track the pointer or transcribe
		//     the pointed-to object first.
		//
		// If the pointer is *not* being tracked then, although we have the address of the
		// pointer, we cannot assume the pointer will remain at that address once we return
		// from this transcribe call - by turning off tracking the client is telling us this.
		// In this case the pointed-to load object is not yet loaded and so the pointer can
		// never be changed later when it is loaded (since pointer is untracked) and so the
		// scribe client must ensure this doesn't happen.
		GPlatesGlobal::Assert<Exceptions::TranscribedUntrackedPointerBeforeReferencedObject>(
				false,
				GPLATES_ASSERTION_SOURCE,
				// Note that this is the pointed-to object itself and not the pointer...
				*class_info.dereference_type_info.get());
	}

	// Unresolve/un-initialise pointers referencing this object.
	//
	// There will only be pointers referencing this object if we're discarding this object
	// (because if we were only untracking this object then an exception would have been thrown above).
	//
	// It's not an error (yet) because the object may get transcribed again later (and succeed) in
	// which case its referenced pointers will get re-resolved again (otherwise the load will fail
	// at the end due to incomplete transcription).
	unresolve_pointers_referencing_object(object_id);

	// If this object (being untracked) is a pointer then remove itself from the pointed-to object's
	// list of referencing pointers.
	remove_pointer_referencing_object(object_id);

	// Remove this object from the child/sub/base lists of the parent object (if has a parent).
	remove_child_object_from_parent(object_id);

	// Remove this object from the parent pointer of the child objects (if has any).
	remove_parent_object_from_children(object_id);

	// If the object is being discarded and is a pointer that currently references another object then
	// set it to NULL in case clients accidentally try to use it.
	if (discard &&
		object_info.object_referenced_by_pointer)
	{
		unresolve_pointer_reference_to_object(object_id/*pointer_object_id*/);
	}

	// Remove the object's address from the mapping of tracked addresses.
	d_tracked_object_address_to_id_map.erase(get_object_address(object_id));

	// Reset the object info.
	// This essentially removes all record of the object having been transcribed.
	//
	// It also means that discarded objects won't get checked at the end of loading from archive
	// to see if they are un-initialised (discarded objects get reported directly to the scribe
	// client as a transcribe error code and the client deals directly with that error by
	// recovering or propagating the error up the call chain).
	//
	// If a later attempt is made to transcribe the object again then it will start afresh.
	// For example, if an object with a shared pointer data member failed to load (due to one of its
	// other data members failing) then the entire object will probably fail to load and will be
	// discarded/untracked (including the object pointed-to by the shared pointer). However if
	// another shared pointer is later transcribed it will create the pointed-to object a second
	// time (being unaware it was created a first time and then discarded).
	object_info.untrack();
}


GPlatesScribe::Scribe::ObjectInfo &
GPlatesScribe::Scribe::get_object_info(
		object_id_type object_id)
{
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_id < boost::numeric_cast<object_id_type>(d_object_infos.size()),
			GPLATES_ASSERTION_SOURCE,
			"Object id outside valid range.");

	ObjectInfo *object_info = d_object_infos[object_id];

	// The object id slot might not be used, for example, if an object was not loaded because it
	// was not recognised (due to archive generated by a future GPlates) or if object was untracked
	// because either it failed to load or scribe client explicitly requested it be untracked.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_info,
			GPLATES_ASSERTION_SOURCE,
			"Object id is unused.");

	return *d_object_infos[object_id];
}


GPlatesScribe::Scribe::object_address_type
GPlatesScribe::Scribe::get_object_address(
		object_id_type object_id)
{
	boost::optional<object_address_type> object_address = find_object_address(object_id);

	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_address,
			GPLATES_ASSERTION_SOURCE,
			"Cannot get address of object with no recorded address or type info.");

	return object_address.get();
}


boost::optional<GPlatesScribe::Scribe::object_address_type>
GPlatesScribe::Scribe::find_object_address(
		object_id_type object_id)
{
	// The object info of the specified object.
	ObjectInfo &object_info = get_object_info(object_id);

	// The class info associated with the specified object.
	ClassInfo &class_info = get_class_info_from_object(object_id);

	if (!object_info.object_address ||
		!class_info.object_type_info)
	{
		return boost::none;
	}

	return object_address_type(
			object_info.object_address.get(),
			*class_info.object_type_info.get());
}


GPlatesScribe::Scribe::object_id_type
GPlatesScribe::Scribe::get_object_id(
		const object_address_type &object_address)
{
	boost::optional<object_id_type> object_id = find_object_id(object_address);

	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_id,
			GPLATES_ASSERTION_SOURCE,
			"Object's address has not yet been recorded (object not yet transcribed).");

	return object_id.get();
}


boost::optional<GPlatesScribe::Scribe::object_id_type>
GPlatesScribe::Scribe::find_object_id(
		const object_address_type &object_address)
{
	// See if the object is tracked (yet).
	tracked_object_address_to_id_map_type::iterator object_address_iter =
			d_tracked_object_address_to_id_map.find(object_address);

	if (object_address_iter == d_tracked_object_address_to_id_map.end())
	{
		return boost::none;
	}

	return object_address_iter->second;
}


GPlatesScribe::Scribe::class_id_type
GPlatesScribe::Scribe::get_or_create_class_id(
		const std::type_info &class_type)
{
	// Attempt to insert the class type into the class-type-to-id map.
	const std::pair<class_type_to_id_map_type::iterator, bool> class_type_inserted =
			d_class_type_to_id_map.insert(
					class_type_to_id_map_type::value_type(
							&class_type,
							0/* dummy class id */));

	class_id_type class_id;

	if (class_type_inserted.second)
	{
		// Insertion was successful so create a new class info.
		class_id = create_new_class_info();

		// Assign new class id to the inserted map entry.
		class_type_inserted.first->second = class_id;
	}
	else // not inserted...
	{
		// The class type already exists in the map (insertion failed) so use its class id.
		class_id = class_type_inserted.first->second;
	}

	return class_id;
}


GPlatesScribe::Scribe::class_id_type
GPlatesScribe::Scribe::create_new_class_info()
{
	// Get the next class id.
	const class_id_type new_class_id = d_class_infos.size();

	// Create a ClassInfo from the pool.
	ClassInfo *new_class_info = d_class_info_pool.construct(new_class_id);

	// Add to the list.
	d_class_infos.push_back(new_class_info);

	return new_class_id;
}


GPlatesScribe::Scribe::ClassInfo &
GPlatesScribe::Scribe::get_class_info(
		class_id_type class_id)
{
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			class_id < d_class_infos.size(),
			GPLATES_ASSERTION_SOURCE,
			"Class id outside valid range.");

	return *d_class_infos[class_id];
}


GPlatesScribe::Scribe::ClassInfo &
GPlatesScribe::Scribe::get_class_info_from_object(
		object_id_type object_id)
{
	// The object info of the specified object.
	ObjectInfo &object_info = get_object_info(object_id);

	// Shouldn't be able to get here without a valid class id.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_info.class_id,
			GPLATES_ASSERTION_SOURCE,
			"Object does not have a class id.");

	// The class info associated with the specified object.
	return get_class_info(object_info.class_id.get());
}


boost::optional<GPlatesScribe::Scribe::transcribe_context_stack_type &>
GPlatesScribe::Scribe::get_transcribe_context_stack(
		const std::type_info &class_type_info)
{
	// Look up the class id from the class type.
	class_type_to_id_map_type::iterator class_type_iter =
			d_class_type_to_id_map.find(&class_type_info);

	if (class_type_iter == d_class_type_to_id_map.end())
	{
		return boost::none;
	}

	const class_id_type class_id = class_type_iter->second;

	// Get the class info.
	ClassInfo &class_info = get_class_info(class_id);

	return class_info.transcribe_context_stack;
}


void
GPlatesScribe::Scribe::push_transcribed_object(
		object_id_type transcribed_object_id)
{
	// If we're not transcribing at the root level then we will have a parent transcribed object and we will:
	//  1) Set the transcribed parent object of the transcribed object, and
	//  2) Add the transcribed object to the transcribed parent's child-objects list, and
	//     to the sub-objects list if lies within memory area of parent).
	if (!d_transcribed_object_stack.empty())
	{
		ObjectInfo &transcribed_object_info = get_object_info(transcribed_object_id);

		// Get the transcribed parent object.
		const object_id_type transcribed_parent_object_id = d_transcribed_object_stack.top();

		// Set the parent object.
		transcribed_object_info.parent_object = transcribed_parent_object_id;

		// Add as a child-object of the parent.
		add_child_object_to_parent(transcribed_object_id);

		// Add as a sub-object of the parent (if lies within memory area of parent).
		//
		// Note that if the transcribed object does not currently lie within the memory area of the
		// parent (and hence is not yet a sub-object of parent) it can still become a sub-object
		// later if the client relocates it from outside the parent's object memory area to inside.
		// In which case it gets added as a sub-object when that relocation happens.
		// This can happen for parent objects with no default constructor - they need to implement
		// 'transcribe_construct_data()' (as well as 'transcribe()') in order to transcribe their
		// constructor parameters - these constructor parameters are initially outside the parent
		// object's memory area but subsequently get relocated inside the parent object.
		add_child_as_sub_object_if_inside_parent(transcribed_object_id);
	}

	// The child-object now becomes the parent object.
	d_transcribed_object_stack.push(transcribed_object_id);

	// Prepare the transcription for a new transcribed object.
	d_transcription_context.push_transcribed_object(transcribed_object_id);
}


void
GPlatesScribe::Scribe::pop_transcribed_object(
		object_id_type transcribed_object_id)
{
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			!d_transcribed_object_stack.empty() &&
				d_transcribed_object_stack.top() == transcribed_object_id,
			GPLATES_ASSERTION_SOURCE,
			"Unexpected transcribed child-object.");

	// The previous parent object now becomes the current parent object.
	d_transcribed_object_stack.pop();

	// Release the current transcribed object from the transcription.
	d_transcription_context.pop_transcribed_object();
}


boost::optional<GPlatesScribe::Scribe::ObjectInfo &>
GPlatesScribe::Scribe::get_current_transcribed_object()
{
	// If we're not transcribing at the root level then we will have a parent transcribed object.
	if (d_transcribed_object_stack.empty())
	{
		return boost::none;
	}

	// Get the current transcribed object.
	const object_id_type transcribed_object_id = d_transcribed_object_stack.top();

	return get_object_info(transcribed_object_id);
}


bool
GPlatesScribe::Scribe::is_child_object_inside_parent_object_memory(
		object_id_type child_object_id,
		object_id_type parent_object_id)
{
	ObjectInfo &parent_object_info = get_object_info(parent_object_id);

	// The class info of the parent object.
	ClassInfo &parent_class_info = get_class_info_from_object(parent_object_id);

	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			parent_object_info.object_address,
			GPLATES_ASSERTION_SOURCE,
			"Parent object does not have its address recorded.");

	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			parent_class_info.object_size,
			GPLATES_ASSERTION_SOURCE,
			"Parent object does not have its size recorded.");

	// Begin address of the parent object.
	const void *parent_object_begin_address = parent_object_info.object_address.get();

	// Calculate the address at the end of the parent object.
	const void *parent_object_end_address = reinterpret_cast<const void *>(
			reinterpret_cast<std::size_t>(parent_object_begin_address) +
					parent_class_info.object_size.get());

	const object_address_type child_object_address = get_object_address(child_object_id);

	// Check if the child object is contained *inline* within the parent object.
	return child_object_address.address >= parent_object_begin_address &&
		child_object_address.address < parent_object_end_address;
}


void
GPlatesScribe::Scribe::add_child_as_sub_object_if_inside_parent(
		object_id_type child_object_id)
{
	ObjectInfo &child_object_info = get_object_info(child_object_id);

	// Get the parent object - if has one.
	boost::optional<object_id_type> parent_object_id = child_object_info.parent_object;
	if (!parent_object_id)
	{
		return;
	}

	if (!is_child_object_inside_parent_object_memory(child_object_id, parent_object_id.get()))
	{
		// Child is not *inside* its parent.
		return;
	}

	ObjectInfo &parent_object_info = get_object_info(parent_object_id.get());

	// The child-object is contained *inline* within the parent object so it will be a sub-object and
	// will be directly affected by relocation of the parent object.
	// In other words the sub-object will also get relocated.
	//
	// When the sub-object is a pointer to another object then, since the pointed-to object is
	// not contained *inline*, the pointed-to object will not, in turn, be a sub-object of the pointer.

	// Allocate a list node from the pool allocator and place the sub-object's id in the node.
	object_ids_list_type::Node *sub_object_id_list_node =
			d_object_ids_list_node_pool.construct(
					object_ids_list_type::Node(child_object_id));

	// Add to the parent's list.
	parent_object_info.sub_objects.append(*sub_object_id_list_node);
}


void
GPlatesScribe::Scribe::remove_child_as_sub_object_if_outside_parent(
		object_id_type child_object_id)
{
	ObjectInfo &child_object_info = get_object_info(child_object_id);

	// Get the parent object - if has one.
	boost::optional<object_id_type> parent_object_id = child_object_info.parent_object;
	if (!parent_object_id)
	{
		return;
	}

	if (is_child_object_inside_parent_object_memory(child_object_id, parent_object_id.get()))
	{
		// Child is not *outside* its parent.
		return;
	}

	ObjectInfo &parent_object_info = get_object_info(parent_object_id.get());

	// The child-object is not contained *inline* within the parent object so it will no longer be a
	// sub-object and will no longer be directly affected by relocation of the parent object.

	// Find and remove from parent's sub-objects.
	object_ids_list_type::iterator parent_sub_objects_iter = parent_object_info.sub_objects.begin();
	object_ids_list_type::iterator parent_sub_objects_end = parent_object_info.sub_objects.end();
	for ( ; parent_sub_objects_iter != parent_sub_objects_end; ++parent_sub_objects_iter)
	{
		const object_id_type parent_sub_object_id = *parent_sub_objects_iter;

		if (parent_sub_object_id == child_object_id)
		{
			// Found child object node in sub-objects list so remove it.
			parent_sub_objects_iter.get()->splice_self_out();
			break;
		}
	}
}


void
GPlatesScribe::Scribe::add_or_remove_relocated_child_as_sub_object_if_inside_or_outside_parent(
		object_id_type relocated_object_id)
{
	// The relocated object.
	ObjectInfo &relocated_object_info = get_object_info(relocated_object_id);

	// Get the parent object - if has one.
	boost::optional<object_id_type> parent_object_id = relocated_object_info.parent_object;
	if (!parent_object_id)
	{
		return;
	}
	ObjectInfo &parent_object_info = get_object_info(parent_object_id.get());

	bool is_sub_object_of_parent = false;

	// See if the relocated object is already a sub-object of its parent.
	object_ids_list_type::const_iterator parent_sub_objects_iter = parent_object_info.sub_objects.begin();
	object_ids_list_type::const_iterator parent_sub_objects_end = parent_object_info.sub_objects.end();
	for ( ; parent_sub_objects_iter != parent_sub_objects_end; ++parent_sub_objects_iter)
	{
		const object_id_type parent_sub_object_id = *parent_sub_objects_iter;

		if (parent_sub_object_id == relocated_object_id)
		{
			// Already is a sub-object.
			is_sub_object_of_parent = true;
			break;
		}
	}

	if (is_sub_object_of_parent)
	{
		// It's already a sub-object so remove it if it is no longer.
		remove_child_as_sub_object_if_outside_parent(relocated_object_id);
	}
	else
	{
		// It's not a sub-object so add it if it is now.
		add_child_as_sub_object_if_inside_parent(relocated_object_id);
	}
}


void
GPlatesScribe::Scribe::remove_parent_object_from_children(
		object_id_type parent_object_id)
{
	ObjectInfo &parent_object_info = get_object_info(parent_object_id);

	// Iterate over the child-objects.
	object_ids_list_type::const_iterator child_objects_iter = parent_object_info.child_objects.begin();
	object_ids_list_type::const_iterator child_objects_end = parent_object_info.child_objects.end();
	for ( ; child_objects_iter != child_objects_end; ++child_objects_iter)
	{
		// Get the child-object id.
		const object_id_type child_object_id = *child_objects_iter;

		ObjectInfo &child_object_info = get_object_info(child_object_id);

		// Remove the parent pointer from the child.
		child_object_info.parent_object = boost::none;
	}
}


void
GPlatesScribe::Scribe::add_child_object_to_parent(
		object_id_type child_object_id)
{
	ObjectInfo &child_object_info = get_object_info(child_object_id);

	// Get the parent object - if has one.
	boost::optional<object_id_type> parent_object_id = child_object_info.parent_object;
	if (!parent_object_id)
	{
		return;
	}

	// Allocate a list node from the pool allocator and place the child-object's id in the node.
	object_ids_list_type::Node *child_object_id_list_node =
			d_object_ids_list_node_pool.construct(
					object_ids_list_type::Node(child_object_id));

	ObjectInfo &parent_object_info = get_object_info(parent_object_id.get());

	// Add to the parent's list.
	parent_object_info.child_objects.append(*child_object_id_list_node);
}


void
GPlatesScribe::Scribe::remove_child_object_from_parent(
		object_id_type child_object_id)
{
	ObjectInfo &child_object_info = get_object_info(child_object_id);

	// Get the parent object - if has one.
	boost::optional<object_id_type> parent_object_id = child_object_info.parent_object;
	if (!parent_object_id)
	{
		return;
	}

	ObjectInfo &parent_object_info = get_object_info(parent_object_id.get());

	// Find and remove from parent's child-objects.
	object_ids_list_type::iterator parent_child_objects_iter = parent_object_info.child_objects.begin();
	object_ids_list_type::iterator parent_child_objects_end = parent_object_info.child_objects.end();
	for ( ; parent_child_objects_iter != parent_child_objects_end; ++parent_child_objects_iter)
	{
		const object_id_type parent_child_object_id = *parent_child_objects_iter;

		if (parent_child_object_id == child_object_id)
		{
			// Found child object node in list so remove it.
			parent_child_objects_iter.get()->splice_self_out();
			break;
		}
	}

	// Find and remove from parent's sub-objects.
	object_ids_list_type::iterator parent_sub_objects_iter = parent_object_info.sub_objects.begin();
	object_ids_list_type::iterator parent_sub_objects_end = parent_object_info.sub_objects.end();
	for ( ; parent_sub_objects_iter != parent_sub_objects_end; ++parent_sub_objects_iter)
	{
		const object_id_type parent_sub_object_id = *parent_sub_objects_iter;

		if (parent_sub_object_id == child_object_id)
		{
			// Found child object node in list so remove it.
			parent_sub_objects_iter.get()->splice_self_out();
			break;
		}
	}

	// Find and remove from parent's base-objects.
	object_ids_list_type::iterator parent_base_class_sub_objects_iter = parent_object_info.base_class_sub_objects.begin();
	object_ids_list_type::iterator parent_base_class_sub_objects_end = parent_object_info.base_class_sub_objects.end();
	for ( ; parent_base_class_sub_objects_iter != parent_base_class_sub_objects_end; ++parent_base_class_sub_objects_iter)
	{
		const object_id_type parent_base_class_sub_object_id = *parent_base_class_sub_objects_iter;

		if (parent_base_class_sub_object_id == child_object_id)
		{
			// Found child object node in list so remove it.
			parent_base_class_sub_objects_iter.get()->splice_self_out();
			break;
		}
	}
}


void
GPlatesScribe::Scribe::add_pointer_referencing_object(
		object_id_type object_id,
		object_id_type pointer_object_id)
{
	ObjectInfo &object_info = get_object_info(object_id);

	// Allocate a list node from the node pool allocator.
	// Place the pointer's object id in the node.
	object_ids_list_type::Node *list_node =
			d_object_ids_list_node_pool.construct(
					object_ids_list_type::Node(pointer_object_id));

	// Add our pointer to the list.
	object_info.pointers_referencing_object.append(*list_node);

	ObjectInfo &pointer_object_info = get_object_info(pointer_object_id);

	// Record the object id in the pointer object info.
	pointer_object_info.object_referenced_by_pointer = object_id;
}


void
GPlatesScribe::Scribe::remove_pointer_referencing_object(
		object_id_type pointer_object_id)
{
	ObjectInfo &pointer_object_info = get_object_info(pointer_object_id);

	// If the pointer is not referencing an object (or its not even a pointer) then return.
	if (!pointer_object_info.object_referenced_by_pointer)
	{
		return;
	}

	ObjectInfo &object_info = get_object_info(pointer_object_info.object_referenced_by_pointer.get());

	// Find and remove the pointer from the pointed-to object's list of pointers.
	object_ids_list_type::iterator pointer_objects_iter = object_info.pointers_referencing_object.begin();
	object_ids_list_type::iterator pointer_objects_end = object_info.pointers_referencing_object.end();
	for ( ; pointer_objects_iter != pointer_objects_end; ++pointer_objects_iter)
	{
		const object_id_type referencing_pointer_object_id = *pointer_objects_iter;

		if (referencing_pointer_object_id == pointer_object_id)
		{
			// Found pointer object node in list so remove it.
			pointer_objects_iter.get()->splice_self_out();
			break;
		}
	}
}


void
GPlatesScribe::Scribe::resolve_pointers_referencing_object(
		object_id_type object_id)
{
	ObjectInfo &object_info = get_object_info(object_id);

	// Iterate over the pointers referencing the object.
	object_ids_list_type::const_iterator pointers_referencing_object_iter =
			object_info.pointers_referencing_object.begin();
	object_ids_list_type::const_iterator pointers_referencing_object_end =
			object_info.pointers_referencing_object.end();
	for ( ;
		pointers_referencing_object_iter != pointers_referencing_object_end;
		++pointers_referencing_object_iter)
	{
		// Get the pointer object id.
		const object_id_type pointer_object_id = *pointers_referencing_object_iter;

		// Set the pointer to point to the object's address.
		//
		// Each pointer is either:
		//  (1) Unresolved: does not yet point to an object and will get initialised here, or
		//  (2) Resolved: already points to an object and will point to a new object address here.
		// Case (2) happens when a transcribed object is relocated (all pointers to it must adjust).
		resolve_pointer_reference_to_object(object_id, pointer_object_id);
	}
}


void
GPlatesScribe::Scribe::resolve_pointer_reference_to_object(
		object_id_type object_id,
		object_id_type pointer_object_id)
{
	//
	// The pointed-to object.
	//
	ObjectInfo &object_info = get_object_info(object_id);

	// Shouldn't be able to get here without a valid object address.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			object_info.object_address,
			GPLATES_ASSERTION_SOURCE,
			"Pointer is referencing object before its address is available.");

	// The class info of the pointed-to object.
	ClassInfo &class_info = get_class_info_from_object(object_id);

	// Shouldn't be able to get here without a valid object type info.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			class_info.object_type_info,
			GPLATES_ASSERTION_SOURCE,
			"Pointer is referencing object before its object type info is available.");

	//
	// The pointer.
	//
	ObjectInfo &pointer_object_info = get_object_info(pointer_object_id);

	// Shouldn't be able to get here without a valid pointer address.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			pointer_object_info.object_address,
			GPLATES_ASSERTION_SOURCE,
			"Pointer, referencing object, does not have an address.");

	// The class info of the pointed-to object.
	ClassInfo &pointer_class_info = get_class_info_from_object(pointer_object_id);

	// Shouldn't be able to get here without a valid reference type info.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			pointer_class_info.dereference_type_info,
			GPLATES_ASSERTION_SOURCE,
			"Pointer, referencing object, does not have a dereference type info.");

	//
	// When loading, set the pointer to point to the object.
	//
	if (is_loading())
	{
		// We need to do any pointer fix ups in the presence of multiple inheritance.
		// It's possible that the pointer refers to a base class of a multiply-inherited
		// derived class object and there can be pointer offsets.
		// So we need to use the void cast registry to apply any necessary pointer offsets.
		//
		// Note that the up-cast path should be available because the pointed-to object has
		// already been transcribed (which records base<->derived relationships).
		boost::optional<void *> referenced_object_address =
				d_void_cast_registry.up_cast(
						// Actual type of the pointed-to object...
						*class_info.object_type_info.get(),
						// Our pointer points to this type...
						*pointer_class_info.dereference_type_info.get(),
						// Address of the actual pointed-to object...
						object_info.object_address.get());

		// Throw UnregisteredCast exception if unable to find path between the dynamic pointed-to
		// object type and the static pointer dereference type.
		//
		// The up-cast failed because the actual referenced object type does not inherit directly or
		// indirectly from pointer's static (dereference) type and so we can't legally point to it.
		// This can happen when the actual object is created dynamically (via a base class
		// pointer) and when it was saved on another system.
		// For example:
		//
		//		template <typename T>
		//		class A
		//		{
		//		public:
		//			virtual ~A() { }
		//		};
		//
		//		template <typename T>
		//		class B : public A<T>
		//		{  };
		//
		// ...where saving on machine X with 'std::size_t' typedef'ed to 'unsigned int'...
		//
		//		boost::shared_ptr< A<std::size_t> > b(new B<std::size_t>());
		//		scribe.transcribe(TRANSCRIBE_SOURCE, b, "b", GPlatesScribe::TRACK);
		//		A<std::size_t> *a = b;
		//		scribe.transcribe(TRANSCRIBE_SOURCE, a, "a", GPlatesScribe::TRACK);
		//
		// ...where loading on machine Y with 'std::size_t' typedef'ed to 'unsigned long'...
		//
		//		boost::shared_ptr< A<std::size_t> > b;
		//		scribe.transcribe(TRANSCRIBE_SOURCE, b, "b", GPlatesScribe::TRACK);  // Actually this would fail first
		//		A<std::size_t> *a;
		//		scribe.transcribe(TRANSCRIBE_SOURCE, a, "a", GPlatesScribe::TRACK);
		//
		// ...where 'b' is saved on machine X as 'B<unsigned int>' and 'b' is also loaded on
		// machine Y as 'B<unsigned int>' (since that's the class name stored in the transcription).
		// But on machine Y, 'A<unsigned long> *' cannot reference 'B<unsigned int>' because
		// 'B<unsigned int>' does not inherit from 'A<unsigned long>'.
		//
		GPlatesGlobal::Assert<Exceptions::UnregisteredCast>(
				referenced_object_address,
				GPLATES_ASSERTION_SOURCE,
				*class_info.object_type_info.get(),
				*pointer_class_info.dereference_type_info.get());

		// This address is the address of the pointer (not the object it's pointing to).
		void *&object_pointer = *static_cast<void **>(pointer_object_info.object_address.get());
		object_pointer = referenced_object_address.get();
	}

	// Mark the pointer as initialised.
	pointer_object_info.is_object_post_initialised = true;
}


void
GPlatesScribe::Scribe::unresolve_pointers_referencing_object(
		object_id_type object_id)
{
	ObjectInfo &object_info = get_object_info(object_id);

	// Iterate over the pointers referencing the object.
	object_ids_list_type::const_iterator pointers_referencing_object_iter =
			object_info.pointers_referencing_object.begin();
	object_ids_list_type::const_iterator pointers_referencing_object_end =
			object_info.pointers_referencing_object.end();
	for ( ;
		pointers_referencing_object_iter != pointers_referencing_object_end;
		++pointers_referencing_object_iter)
	{
		// Get the pointer object id.
		const object_id_type pointer_object_id = *pointers_referencing_object_iter;

		// Set the pointer to NULL and mark it as un-initialised.
		//
		// Each pointer is either:
		//  (1) Unresolved: does not yet point to an object and is already NULL, or
		//  (2) Resolved: already points to an object but will point to NULL here.
		unresolve_pointer_reference_to_object(pointer_object_id);
	}
}


void
GPlatesScribe::Scribe::unresolve_pointer_reference_to_object(
		object_id_type pointer_object_id)
{
	//
	// The pointer.
	//
	ObjectInfo &pointer_object_info = get_object_info(pointer_object_id);

	// Shouldn't be able to get here without a valid pointer address.
	GPlatesGlobal::Assert<Exceptions::ScribeLibraryError>(
			pointer_object_info.object_address,
			GPLATES_ASSERTION_SOURCE,
			"Pointer, referencing object, does not have an address.");

	//
	// When loading, set the pointer to NULL.
	//
	if (is_loading())
	{
		// This address is the address of the pointer (not the object it's pointing to).
		void *&object_pointer = *static_cast<void **>(pointer_object_info.object_address.get());
		object_pointer = NULL;
	}

	// Mark the pointer as uninitialised.
	pointer_object_info.is_object_post_initialised = false;
}


namespace GPlatesScribe
{
	struct Scribe::Bool::CheckDeleter
	{
		explicit
		CheckDeleter(
				const GPlatesUtils::CallStack::Trace &transcribe_source_,
				bool require_check_) :
			transcribe_source(transcribe_source_),
			require_check(require_check_),
			has_been_checked(false)
		{  }

		void
		operator()(
				bool *bool_ptr)
		{
			if (require_check)
			{
				// Track the file/line of the call site for exception messages.
				// This is the file/line at which a transcribe call was made which, in turn,
				// returned a 'Bool'.
				GPlatesUtils::CallStackTracker call_stack_tracker(transcribe_source);

				// We shouldn't be throwing any exceptions in deleter, but this exception is to
				// force programmer to correct the program to check validity.
				//
				// We can get double exceptions if an exception came from outside - then the program will
				// just terminate with no exception information if we throw a second exception below.
				// But this is unlikely because the programmer should be checking the return result
				// straight after transcribing an object - and that should provide no window of
				// opportunity for double exceptions to get thrown (outside exception and our
				// has-been-checked exception).


				// Throw exception if the boolean result 'Bool' returned by 'Scribe::transcribe()',
				// or 'Scribe::transcribe_base()', has not been checked by the caller (in the *load* path).
				//
				// If this assertion is triggered then it means:
				//   * A Scribe client has called 'Scribe::transcribe()', or a similar call,
				//     but has not checked the boolean result 'Bool'.
				//
				// To fix this do something like:
				//
				//	if (!scribe.transcribe(...))
				//	{
				//		return scribe.get_transcribe_result();
				//	}
				//
				GPlatesGlobal::Assert<Exceptions::ScribeTranscribeResultNotChecked>(
						has_been_checked,
						GPLATES_ASSERTION_SOURCE);
			}

			boost::checked_delete(bool_ptr);
		}

		GPlatesUtils::CallStack::Trace transcribe_source;
		bool require_check;
		bool has_been_checked;
	};
}


GPlatesScribe::Scribe::Bool::Bool(
		const GPlatesUtils::CallStack::Trace &transcribe_source,
		bool result,
		bool require_check) :
	d_bool(new bool(result), CheckDeleter(transcribe_source, require_check))
{
}


bool
GPlatesScribe::Scribe::Bool::boolean_test() const
{
	// Mark the Bool as having been checked by the client.
	boost::get_deleter<CheckDeleter>(d_bool)->has_been_checked = true;

	return *d_bool;
}
