/*

Copyright (c) 2002-2005, Yauheni Akhotnikau
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

- The name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.

*/

/*!
	\file oess_1/stdsn/h/shptr.hpp
	\brief Класс умного указателя на сериализуемый объект.
*/

#if !defined( _OESS_1__STDSN__SHPTR_HPP_ )
#define _OESS_1__STDSN__SHPTR_HPP_

#include <memory>

#include <oess_1/defs/h/ex.hpp>

#include <oess_1/stdsn/h/declspec.hpp>

#include <oess_1/stdsn/h/serializable.hpp>
#include <oess_1/stdsn/h/errno.hpp>

namespace oess_1 {

namespace stdsn {

/*!
	\since v.1.4.0

	\brief Политика владения указателем на основе подсчета ссылок.

	\note Объект-счетчик ссылок создается только при первом присваивании
		ненулевого указателя. Если же shptr_skeleton_t контролирует
		нулевой указатель, то счетчика ссылок нет.
*/
template< class T >
class	refcountable_policy_t
	:	private cpp_util_2::nocopy_t
	{
	private :
		//! Тип для счетчика ссылок.
		typedef unsigned long ref_count_t;

		//! Счетчик ссылок.
		ref_count_t *	m_ref_count;

		//! Этот указатель контролирует объект.
		T * &	m_pointer_holder;

	public :
		//! Конструктор по-умолчанию.
		/*!
			Не создает счетчика ссылок.
		*/
		explicit refcountable_policy_t( T * & pointer_holder )
			:	m_ref_count( 0 )
			,	m_pointer_holder( pointer_holder )
			{
				m_pointer_holder = 0;
			}

		//! Инициализирующий конструктор.
		/*!
			Создает счетчик ссылок только, если переданный указатель
			отличен от нуля.
		*/
		refcountable_policy_t(
			T * & pointer_holder, T * pointer )
			:	m_ref_count( 0 )
			,	m_pointer_holder( pointer_holder )
			{
				assign( pointer );
			}

		/*!
			Если счетчик ссылок существует, то уменьшает его
			значение. Если в результате значение достигло нуля
			счетчик ссылок удаляется.
		*/
		~refcountable_policy_t()
			{
				release();
			}

		//! Взять под контроль указанный объект.
		/*!
			Предполагается, что этот объект еще никем не
			контролировался.
		*/
		void
		assign( T * ptr )
			{
				release();

				if( ptr )
					{
						m_ref_count = new ref_count_t( 1 );
						m_pointer_holder = ptr;
					}
			}

		//! Создать счетчик ссылок для объекта.
		/*!
			Когда shptr_skeleton_t десериализуется, указатель
			m_pointer_holder уже содержит новое значение. Для которого
			нужно создать новый счетчик ссылок.
		*/
		void
		reassign()
			{
				// Счетчик ссылок должен быть нулевым!
				if( m_ref_count )
					OESS_THROW_PHYSIC(
							err::c_invalid_policy_state_for_reassignment,
							"reassignment when m_ref_count != null!" )

				m_ref_count = new ref_count_t( 1 );
			}

		//! Подключиться к уже созданному объекту.
		/*!
		*/
		void
		link(
			//! Политика, из которой нужно брать указатель на
			//! счетчик ссылок.
			const refcountable_policy_t< T > & owner_policy )
			{
				if( &owner_policy != this )
					{
						release();
  
						if( 0 != ( m_ref_count = owner_policy.m_ref_count ) )
							{
								++( *m_ref_count );
								m_pointer_holder = owner_policy.m_pointer_holder;
							}
					}
			}

		//! Отказаться от контроля указанного объекта.
		/*!
			\return объект, который удалит объект по указателю \a ptr.
				Указатель удаляется только, если мы являемся его
				единоличными владельцами. В противном случае объект
				по указателю \a ptr не удаляется.
		*/
		std::auto_ptr< T >
		release()
			{
				std::auto_ptr< T > result;
				if( m_ref_count )
					{
						if( 0 == ( --(*m_ref_count) ) )
							{
								ref_count_t * to_be_destroyed = m_ref_count;
								m_ref_count = 0;

								delete to_be_destroyed;

								result = std::auto_ptr< T >( m_pointer_holder );
								m_pointer_holder = 0;
							}
						else
							// Кто-то другой уничтожит m_ref_count.
							m_ref_count = 0;
					}

				return result;
			}
	};

/*!
	\since v.1.4.0

	\brief Класс политики умного указателя, основанной на
		клонировании объектов при копировании.

	У класса-параметра шаблона должен быть константный метод
	clone(), который возвращает указатель на динамически
	созданный объект-копию:
\code
T * T::clone() const;
\endcode
	или через std::auto_ptr:
\code
std::auto_ptr< T > T::clone() const;
\endcode

	\note В данном классе очень важно, чтобы конструкторы
		выставляли значение m_pointer_holder в ноль перед выполнением
		любых других действий.
*/
template< class T >
class	cloneable_policy_t
	:	private cpp_util_2::nocopy_t
	{
	private :
		//! Этот указатель контролирует объект.
		T * &	m_pointer_holder;

	public :
		//! Конструктор по-умолчанию.
		explicit cloneable_policy_t( T * & pointer_holder )
			:	m_pointer_holder( pointer_holder )
			{
				m_pointer_holder = 0;
			}

		//! Инициализирующий конструктор.
		/*!
			Сохраняет указатель без клонирования. Предполагается, что
			только эта политика будет владать данным указателем.
		*/
		cloneable_policy_t(
			T * & pointer_holder, T * pointer )
			:	m_pointer_holder( pointer_holder )
			{
				m_pointer_holder = 0;
				assign( pointer );
			}

		/*!
			Если счетчик ссылок существует, то уменьшает его
			значение. Если в результате значение достигло нуля
			счетчик ссылок удаляется.
		*/
		~cloneable_policy_t()
			{
				release();
			}

		//! Взять под контроль указанный объект.
		/*!
			Предполагается, что этот объект еще никем не
			контролировался.
		*/
		void
		assign( T * ptr )
			{
				release();

				m_pointer_holder = ptr;
			}

		//! Пустой метод.
		/*!
			Ничего не делает, но должен присутствовать в интерфейсе
			класса для того, чтобы shptr_skeleton_t мог вызывать его
			в своем методе oess_post_unpack().

			Делать ничего не нужно, т.к. m_pointer_holder указывает на
			объект, единоличным владельцем которого является как раз
			данный объект политика.
		*/
		void
		reassign()
			{
			}

		//! Подключиться к уже созданному объекту.
		/*!
			Сохраняет в m_pointer_holder клонированную копию второго
			объекта.
		*/
		void
		link(
			//! Политика, из которой нужно брать указатель на
			//! счетчик ссылок.
			const cloneable_policy_t< T > & owner_policy )
			{
				if( &owner_policy != this )
					{
						release();
  
						if( owner_policy.m_pointer_holder )
							{
								std::auto_ptr< T > clone( 
										owner_policy.m_pointer_holder->clone() );
								m_pointer_holder = clone.release();
							}
					}
			}

		//! Отказаться от контроля указанного объекта.
		/*!
			\return объект, который удалит объект по указателю
					\a m_pointer_holder.
		*/
		std::auto_ptr< T >
		release()
			{
				std::auto_ptr< T > result( m_pointer_holder );
				m_pointer_holder = 0;

				return result;
			}
	};

/*!
	\since v.1.4.0
	\brief Тег, который должен показывать, к какому типу нужно
		делать кастинг в классе умного указателя.

	Пример использования можно посмотреть в описании класса
	oess_1::stdsn::shptr_skeleton_t.
*/
template< class T >
struct shptr_type_tag {};

/*!
	\since v.1.4.0
	\brief Каркас для реализации функциональности умных указателей.

	\note Для приведения контролируемого указателя к нужному типу
		следует передавать в качестве параметра в метод
		cast_to() объект shptr_type_tag, инстанциированный нужным
		типом:
\code
some_type_t * p = some_smart_ptr.cast_to(
		oess_1::stdsn::shptr_type_tag< some_type_t > );
\endcode

	Более подробную информацию можно получить в
	\ref oess_1_4_0__stdsn__shptr_skeleton.
*/
template<
		class T,
		class Own_policy = refcountable_policy_t< T > >
class	shptr_skeleton_t
	:	public oess_1::stdsn::serializable_t
	{
		typedef shptr_skeleton_t< T, Own_policy >	self_type_t;

	protected :
		//! Указатель, которым мы должны владеть.
		T *	m_ptr;

	private :
		//! Политика владения указателем.
		Own_policy	m_own_policy;

	public :
		//! Конструктор по умолчанию.
		/*!
			Указывает, что нет сериализуемого объекта.
		*/
		shptr_skeleton_t()
			:	m_ptr( 0 )
			,	m_own_policy( m_ptr )
			{}

		//! Конструктор при наличии сериализуемого объекта.
		shptr_skeleton_t(
			//! Указатель на динамически созданный объект,
			//! который будет уничтожен в после уничтожения
			//! всех умных указателей на него.
			T * ptr )
			:	m_ptr( 0 )
			,	m_own_policy( m_ptr, ptr )
			{}

		//! Конструктор копирования.
		shptr_skeleton_t(
			const self_type_t & o )
			:	m_ptr( 0 )
			,	m_own_policy( m_ptr )
			{
				(*this) = o;
			}
		virtual ~shptr_skeleton_t()
			{}

		//! Оператор копирования.
		self_type_t &
		operator=(
			const self_type_t & o )
			{
				m_own_policy.link( o.m_own_policy );
				return *this;
			}

		//! \name Методы базового класса, которые должны остаться виртуальными.
		/*! \{ */
		virtual void
		oess_pack( oess_1::stdsn::oent_t & writter ) const = 0;

		virtual void
		oess_unpack( oess_1::stdsn::ient_t & reader ) = 0;

		virtual void *
		oess_cast( const std::string & type_name ) = 0;

		virtual const std::string &
		oess_type_name() const = 0;

		virtual const std::string &
		oess_scheme() const = 0;

		virtual void
		oess_get_subclass_extension_path(
			oess_1::stdsn::subclass_extension_path_t & path,
			const std::string & terminator ) const = 0;
		/*! \} */

		/*! @{ */
		//! Приведение указателя на сериализуемый объект.
		/*!
			Для приведения используется метод
			serializable_t::oess_cast() сериализуемого
			объекта.

			\throw oess_1::ex_t если приведение не возможно.
		*/
		template< class Y >
		Y *
		cast_to( const shptr_type_tag< Y > & )
			{
				typedef typename Y::oess_serializer_t	serializer_t;
				if( m_ptr )
					{
						Y * p = reinterpret_cast< Y * >(
								m_ptr->oess_cast( serializer_t::type_name() ) );
						if( !p )
							OESS_THROW_LOGIC( err::c_cast_failed,
									"unable cast from '" << m_ptr->oess_type_name()
									<< "' to '" << serializer_t::type_name() )
						return p;
					}

				return 0;
			}

		//! Приведение указателя на сериализуемый объект.
		/*!
			Для приведения используется метод
			serializable_t::oess_cast() сериализуемого
			объекта.

			\throw oess_1::ex_t если приведение не возможно.
		*/
		template< class Y >
		const Y *
		cast_to( const shptr_type_tag< Y > & ) const
			{
				typedef typename Y::oess_serializer_t	serializer_t;
				if( m_ptr )
					{
						const Y * p = reinterpret_cast< const Y * >(
								m_ptr->oess_cast( serializer_t::type_name() ) );
						if( !p )
							OESS_THROW_LOGIC( err::c_cast_failed,
									"unable cast from '" << m_ptr->oess_type_name()
									<< "' to '" << serializer_t::type_name() )
						return p;
					}

				return 0;
			}
		/*! @} */

		//! Попытаться уничтожить контролируемый объект.
		/*!
			Уменьшается количество ссылок на контролируемый
			объект. Если количество ссылок уменьшилось до
			нуля, то контролируемый объект уничтожается.

			В любом случает считается, что экземпляр
			%shptr_skeleton_t больше не контролирует
			сериализуемый объект.
		*/
		void
		destroy()
			{
				m_own_policy.release();
			}

	protected :
		/*!
			Вызывает метод destroy(), т.к. контролируемый объект
			для данного экземпляра %shptr_skeleton_t будет заменен.
		*/
		virtual void
		oess_pre_unpack()
			{
				// После десериализации m_ptr примет новое значение.
				// Поэтому старый контролируемый объект должен быть
				// уничтожен.
				destroy();
			}

		/*!
			Создает новый счетчик ссылок для нового
			контролируемого объекта.
		*/
		virtual void
		oess_post_unpack()
			{
				m_own_policy.reassign();
			}
	};

//
// shptr_t
//

/*!
	\brief Класс умного указателя на сериализуемый объект.

	Предназначен для возможности сериализации объектов, про
	которых известно лишь то, что они производны от
	oess_1::stdsn::serializable_t. На сериализуемый объект
	могут одновременно ссылаться несколько объектов-указателей.
	Сериализуемый объект автоматически
	уничтожается после уничтожения всех ссылающихся на него
	объектов-указателей.

	В отличии от других подобных классов, %shptr_t
	ориентирован на serializable_t и не предоставляет
	операторов разыменования указателя. Вместо этого
	предполагается, что от класса %shptr_t будут
	создаваться производные классы, которые предоставят
	операторы разыменования.

	DDL описание этого типа определено в oess_1/stdsn/ddl/shptr.ddl
	следующим образом:
\verbatim
{type	oess_1::stdsn::shptr_t
	{attr	m_ptr	{of	{ptr}	oess_1::stdsn::serializable_t} }
}
\endverbatim

	\attention Данный класс не является thread-safe.
*/
class	OESS_1__STDSN__TYPE	shptr_t
	:	public shptr_skeleton_t< oess_1::stdsn::serializable_t >
	{
	typedef shptr_skeleton_t< oess_1::stdsn::serializable_t > base_type_t;
	OESS_SERIALIZER_EX( shptr_t, OESS_1__STDSN__TYPE )
	public :
		//! Конструктор по умолчанию.
		/*!
			Указывает, что нет сериализуемого объекта.
		*/
		shptr_t();
		//! Конструктор при наличии сериализуемого объекта.
		shptr_t(
			//! Указатель на динамически созданный объект,
			//! который будет уничтожен в после уничтожения
			//! всех умных указателей на него.
			serializable_t * ptr );
		//! Конструктор копирования.
		shptr_t(
			const shptr_t & o );
		virtual ~shptr_t();
};

//! Макрос для упрощения настройки умных указателей.
/*!
	Класс oess_1::stdsn::shptr_t не предоставляет
	операторов разыменования указателей. Поэтому, его
	использование не так удобно, как, например, класса
	std::auto_ptr. Предполагается, что если требуется
	класс, аналогичный std::auto_ptr для конкретного
	сериализуемого класса, то такой класс будет реализован
	программистом. И в этом классе программист сам
	реализует все необходимые ему средства:

\code
// Какой-то сериализуемый класс.
class	some_class_t : public oess_1::stdsn::serializable_t
{
	...
};
// Умный указатель, настроенный для some_class_t.
class	some_class_ptr_t : public oess_1::stdsn::shptr_t
{
	OESS_SERIALIZER( some_class_ptr_t )
	public :
		some_class_ptr_t();
		some_class_ptr_t(
			some_class_t * p );
		some_class_ptr_t(
			const some_class_ptr_t & o );
		virtual ~some_class_ptr_t();

		some_class_ptr_t &
		operator=( const some_class_ptr_t & o );

		some_class_t *
		get();
		const some_class_t *
		get() const;

		some_class_t *
		operator->() { return get(); }
		const some_class_t *
		operator->() const { return get(); }

		some_class_t &
		operator*() { return *get(); }
		const some_class_t &
		operator*() const { return *get(); }
};
\endcode

	В большинстве случаев содержимое всех конкретных классов
	умных указателей будет одинаковым. Поэтому, для облегчения
	написания подобных классов предназначен макрос
	%OESS_1_SHPTR_IFACE. С его помощью описанный выше
	%some_class_ptr_t можно было написать так:

\code
// Какой-то сериализуемый класс.
class	some_class_t : public oess_1::stdsn::serializable_t
{
	...
};
// Умный указатель, настроенный для some_class_t.
class	some_class_ptr_t : public oess_1::stdsn::shptr_t
{
	OESS_SERIALIZER( some_class_ptr_t )
	OESS_1_SHPTR_IFACE( some_class_ptr_t, some_class_t,
		oess_1::stdsn::shptr_t )
};
\endcode

	\attention Конкретные классы умных указателей необходимо
	описывать в DDL. Например, класс some_class_ptr_t нужно
	описать:
\verbatim
{type	some_class_ptr_t
	{super oess_1::stdsn::shptr_t}
}
\endverbatim

	Макрос %OESS_1_SHPTR_IFACE описывает только методы
	класса, а не весь класс целиком для:
	- предоставления возможности введения в класс
	умного указателя собственных методов;
	- предоставления возможности указания расширений C++
	в описании класса (например, управления экспортом
	класса):
\code
class __declspec(dllexport) some_class_ptr_t :
	public oess_1::stdsn::shptr_t
{
	...
};
\endcode

	\param shptr_type	Имя типа, в котором использован
		макрос (т.е. имя типа умного указателя). Это имя
		необходимо для описания конструкторов и деструктора.
		Поэтому это должно быть короткое (не полностью
		квалифицированное) имя -- например some_class_ptr_t,
		а не my_namespace::some_class_ptr_t.

	\param referred_type	Имя типа, на который указывает
		умный указатель. Должен быть полностью квалифицированным
		именем типа (т.к. это имя передается в метод
		oess_1::stdsn::shptr_t::cast_to(), то оно должно
		быть точно таким, каким указано в DDL).

	\param base_type	Имя типа, от которого произведен
		данный класс умного указателя. Может быть полностью
		квалифицированным именем типа.

	Параметр \a base_type введен в %OESS_1_SHPTR_IFACE для
	того, чтобы можно было создавать классы умных указателей
	для иерархии наследования. При этом сами классы умных
	указателей образовывали бы свою иерархию. Например:

\code
// Базовый тип...
class	my_base_t : public oess_1::stdsn::serializable_t
{
	...
};
// ... и умный указатель для него.
class	my_base_ptr_t : public oess_1::stdsn::shptr_t
{
	OESS_SERIALIZER( my_base_ptr_t )
	OESS_1_SHPTR_IFACE( my_base_ptr_t, my_base_t,
		oess_1::stdsn::shptr_t )
};
// Производный класс...
class	my_derived_t : public my_base_t
{
	...
};
// ... и умный указатель для него.
class	my_derived_ptr_t : public my_base_ptr_t
{
	OESS_SERIALIZER( my_derived_ptr_t )
	OESS_1_SHPTR_IFACE( my_derived_ptr_t, my_derived_t,
		my_base_ptr_t )
};
\endcode

	Благодоря этому ссылку на объект my_derived_ptr_t можно
	передать как ссылку на объект my_base_ptr_t:

\code
void
f( const my_base_ptr_t & ref )
{
	...
}

void
g()
{
	my_derived_ptr_t ptr( new my_derived_t() );
	f( ptr );
}
\endcode

	Макрос %OESS_1_SHPTR_IFACE(\a shptr_type, \a referred_type,
	\a base_type) раскрывается следующим образом:

\code
private :
	typedef base_type base_type_t;

public :
	// Конструкторы и деструктор.
	shptr_type() {}
	shptr_type( referred_type * p ) : base_type_t( p ) {}
	shptr_type( const shptr_type & o ) : base_type_t( o ) {}
	virtual ~shptr_type() {}

	// Оператор копирования.
	shptr_type &
	operator=( const shptr_type & o )
	{ base_type_t::operator=( o ); return *this; }

	// Функции получения указателя, приведенного к нужному типу.
	referred_type *
	get() { return cast_to( shptr_type_tag< referred_type >() ); }
	const referred_type *
	get() const { return cast_to( shptr_type_tag< referred_type > ); }

	referred_type *
	operator->() { return get(); }
	const referred_type *
	operator->() const { return get(); }

	referred_type &
	operator*() { return *get(); }
	const referred_type &
	operator*() const { return *get(); }

private :
\endcode
*/
#define OESS_1_SHPTR_IFACE(shptr_type, referred_type, base_type) \
private : \
	typedef base_type base_type_typedef__; \
public : \
	shptr_type() {} \
	shptr_type( referred_type * p ) : base_type_typedef__( p ) {} \
	shptr_type( const shptr_type & o ) : base_type_typedef__( o ) {} \
	virtual ~shptr_type() {} \
	shptr_type & \
	operator=( const shptr_type & o ) \
	{ base_type_typedef__::operator=( o ); return *this; } \
	referred_type * \
	get() { return cast_to( oess_1::stdsn::shptr_type_tag< referred_type >() ); } \
	const referred_type * \
	get() const { return cast_to( oess_1::stdsn::shptr_type_tag< referred_type >() ); } \
	referred_type * \
	operator->() { return get(); } \
	const referred_type * \
	operator->() const { return get(); } \
	referred_type & \
	operator*() { return *get(); } \
	const referred_type & \
	operator*() const { return *get(); } \
private :

} /* namespace stdsn */

} /* namespace oess_1 */

#endif

