2006-07-18 23:21:48 +08:00
|
|
|
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
|
2005-04-29 18:06:50 +08:00
|
|
|
*
|
|
|
|
* This library is open source and may be redistributed and/or modified under
|
|
|
|
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
|
|
|
|
* (at your option) any later version. The full license is in LICENSE file
|
|
|
|
* included with this distribution, and on the openscenegraph.org website.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* OpenSceneGraph Public License for more details.
|
|
|
|
*/
|
|
|
|
//osgIntrospection - Copyright (C) 2005 Marco Jez
|
|
|
|
|
2004-12-09 13:28:20 +08:00
|
|
|
#ifndef OSGINTROSPECTION_VALUE_
|
|
|
|
#define OSGINTROSPECTION_VALUE_
|
|
|
|
|
|
|
|
#include <osgIntrospection/Export>
|
|
|
|
#include <osgIntrospection/Reflection>
|
2007-02-13 01:14:46 +08:00
|
|
|
#include <osgIntrospection/type_traits>
|
2004-12-09 13:28:20 +08:00
|
|
|
|
|
|
|
#include <vector>
|
|
|
|
#include <memory>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
namespace osgIntrospection
|
|
|
|
{
|
|
|
|
|
2005-03-14 17:28:31 +08:00
|
|
|
class Type;
|
|
|
|
|
|
|
|
class OSGINTROSPECTION_EXPORT Value
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
/// Default constructor. Initializes internal structures
|
|
|
|
/// so that the Type returned by getType() is typeof(void),
|
|
|
|
/// and the value is empty so that isEmpty() returns true.
|
|
|
|
/// Be careful when using empty values, as some operations
|
|
|
|
/// on them may throw an exception.
|
|
|
|
inline Value();
|
|
|
|
|
|
|
|
/// Direct initialization constructor for void pointers.
|
|
|
|
/// Although one of the constructor templates below could
|
|
|
|
/// certainly handle void pointers as well, we need to treat
|
|
|
|
/// them separately because void* can't be dereferenced.
|
|
|
|
inline Value(void *v);
|
|
|
|
|
|
|
|
/// Direct initialization constructor for const void pointers.
|
|
|
|
/// Although one of the constructor templates below could
|
|
|
|
/// certainly handle void pointers as well, we need to treat
|
|
|
|
/// them separately because void* can't be dereferenced.
|
|
|
|
inline Value(const void *v);
|
|
|
|
|
|
|
|
/// Direct initialization constructor template for non-const
|
|
|
|
/// pointers. By initializing an instance of Value through
|
|
|
|
/// this constructor, internal structures will be configured
|
|
|
|
/// to handle polymorphic types. This means you'll be able to
|
|
|
|
/// call getInstanceType() to get the actual type of the
|
|
|
|
/// dereferenced value.
|
|
|
|
template<typename T> Value(T *v);
|
|
|
|
|
|
|
|
/// Direct initialization constructor template for non-const
|
|
|
|
/// pointers. By initializing an instance of Value through
|
|
|
|
/// this constructor, internal structures will be configured
|
|
|
|
/// to handle polymorphic types. This means you'll be able to
|
|
|
|
/// call getInstanceType() to get the actual type of the
|
|
|
|
/// dereferenced value.
|
|
|
|
template<typename T> Value(const T *v);
|
|
|
|
|
|
|
|
/// Direct initialization constructor template for all types
|
|
|
|
/// that are not handled by any of the constructors above.
|
|
|
|
/// Calling getInstanceType() on an instance constructed
|
|
|
|
/// this way returns the same as getType().
|
|
|
|
template<typename T> Value(const T &v);
|
|
|
|
|
|
|
|
/// Copy constructor. The underlying value's type must have
|
|
|
|
/// consistent copy semantics.
|
2005-04-29 19:19:58 +08:00
|
|
|
inline Value(const Value& copy);
|
2005-03-14 17:28:31 +08:00
|
|
|
|
|
|
|
/// Destructor. Frees internal resources but it does NOT delete
|
|
|
|
/// the value held. For example, this function will produce a
|
|
|
|
/// memory leak: void f() { Value v(new int); }
|
|
|
|
inline ~Value();
|
|
|
|
|
|
|
|
/// Assignment operator. Behaves like the copy constructor.
|
2005-04-29 19:19:58 +08:00
|
|
|
inline Value& operator=(const Value& copy);
|
2005-03-14 17:28:31 +08:00
|
|
|
|
|
|
|
/// Returns whether the value is a pointer and it points to
|
|
|
|
/// something whose type is different than void.
|
|
|
|
inline bool isTypedPointer() const;
|
|
|
|
|
|
|
|
/// Returns whether this Value is empty.
|
|
|
|
inline bool isEmpty() const;
|
|
|
|
|
|
|
|
/// Returns whether the value is a null pointer.
|
|
|
|
inline bool isNullPointer() const;
|
|
|
|
|
|
|
|
/// Returns the exact type of the value held.
|
2005-04-29 19:19:58 +08:00
|
|
|
inline const Type& getType() const;
|
2005-03-14 17:28:31 +08:00
|
|
|
|
|
|
|
/// If the value is a pointer to a non-void type, this method
|
|
|
|
/// returns the actual type of the dereferenced pointer. Please
|
|
|
|
/// note it is not the same as getType().getPointedType(),
|
|
|
|
/// because the latter would return the non-polymorphic type.
|
|
|
|
/// If the value is not a pointer, this method behaves like
|
|
|
|
/// getType().
|
2005-04-29 19:19:58 +08:00
|
|
|
inline const Type& getInstanceType() const;
|
2005-03-14 17:28:31 +08:00
|
|
|
|
2005-04-04 21:50:07 +08:00
|
|
|
/// Equal to operator.
|
2005-04-29 19:19:58 +08:00
|
|
|
bool operator==(const Value& other) const;
|
2005-03-14 17:28:31 +08:00
|
|
|
|
2005-04-04 21:50:07 +08:00
|
|
|
/// Less than or equal to operator.
|
2005-04-29 19:19:58 +08:00
|
|
|
bool operator<=(const Value& other) const;
|
2005-04-04 21:50:07 +08:00
|
|
|
|
2005-03-14 17:28:31 +08:00
|
|
|
/// Inequality test operator. Returns !operator==(other).
|
2005-04-29 19:19:58 +08:00
|
|
|
bool operator!=(const Value& other) const;
|
2005-04-04 21:50:07 +08:00
|
|
|
|
|
|
|
/// Greater than operator. Returns !operator<=(other).
|
2005-04-29 19:19:58 +08:00
|
|
|
bool operator>(const Value& other) const;
|
2005-03-14 17:28:31 +08:00
|
|
|
|
2005-04-04 21:50:07 +08:00
|
|
|
/// Less than operator. Returns !operator==(other) && operator<=(other).
|
2005-04-29 19:19:58 +08:00
|
|
|
bool operator<(const Value& other) const;
|
2005-03-14 17:28:31 +08:00
|
|
|
|
2005-04-04 21:50:07 +08:00
|
|
|
/// Greater than or equal to operator. Returns operator==(other) || !operator<=(other)
|
2005-04-29 19:19:58 +08:00
|
|
|
bool operator>=(const Value& other) const;
|
2005-04-04 21:50:07 +08:00
|
|
|
|
2005-03-14 17:28:31 +08:00
|
|
|
/// Tries to convert this instance to a Value of the given type.
|
|
|
|
/// The conversion is performed by rendering to a temporary stream
|
|
|
|
/// in the source format and trying to read back from the stream
|
|
|
|
/// in the destination format. If either the source or destination
|
|
|
|
/// types, or both, don't have a ReaderWriter object, the conversion
|
|
|
|
/// fails and an exception is thrown. If the conversion can't be
|
|
|
|
/// completed for other reasons, other exceptions may be thrown.
|
2005-04-29 19:19:58 +08:00
|
|
|
Value convertTo(const Type& outtype) const;
|
2005-03-14 17:28:31 +08:00
|
|
|
|
|
|
|
/// Tries to convert this instance to a Value of the given type.
|
|
|
|
/// The conversion is performed by rendering to a temporary stream
|
|
|
|
/// in the source format and trying to read back from the stream
|
|
|
|
/// in the destination format. If either the source or destination
|
|
|
|
/// types, or both, don't have a ReaderWriter object, the conversion
|
|
|
|
/// fails and an empty Value is returned.
|
|
|
|
/// Please note that unlike convertTo(), this method does not
|
|
|
|
/// intentionally throw any exceptions.
|
2005-04-29 19:19:58 +08:00
|
|
|
Value tryConvertTo(const Type& outtype) const;
|
2005-03-14 17:28:31 +08:00
|
|
|
|
|
|
|
/// Tries to get a string representation of the underlying value.
|
|
|
|
/// This requires the value's type to have a ReaderWriter object
|
|
|
|
/// associated to it. If the conversion can't be completed, an
|
|
|
|
/// exception is thrown.
|
|
|
|
std::string toString() const;
|
2006-09-01 20:52:15 +08:00
|
|
|
std::wstring toWString() const;
|
2005-04-04 21:50:07 +08:00
|
|
|
|
|
|
|
/// Swaps the content of this Value with another Value
|
2005-04-29 19:19:58 +08:00
|
|
|
void swap(Value& v);
|
2005-03-14 17:28:31 +08:00
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
// It's good to have friends!
|
2005-04-29 19:19:58 +08:00
|
|
|
template<typename T> friend T variant_cast(const Value& v);
|
|
|
|
template<typename T> friend bool requires_conversion(const Value& v);
|
|
|
|
template<typename T> friend T *extract_raw_data(Value& v);
|
|
|
|
template<typename T> friend const T *extract_raw_data(const Value& v);
|
2005-03-14 17:28:31 +08:00
|
|
|
|
|
|
|
// throw an exception if the value is empty
|
|
|
|
void check_empty() const;
|
|
|
|
|
|
|
|
// Base class for holding values. Provides a clone() method
|
|
|
|
// which must be overriden in descendant classes.
|
|
|
|
struct Instance_base
|
|
|
|
{
|
|
|
|
virtual Instance_base *clone() const = 0;
|
|
|
|
virtual ~Instance_base() {}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Generic descendant of Instance_base for holding values of
|
|
|
|
// type T. Note that values are created on the stack.
|
|
|
|
template<typename T>
|
|
|
|
struct Instance: Instance_base
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
Instance(T data): _data(data) {}
|
2005-03-14 17:28:31 +08:00
|
|
|
virtual Instance_base *clone() const { return new Instance<T>(*this); }
|
|
|
|
virtual ~Instance() {}
|
2005-04-29 19:19:58 +08:00
|
|
|
T _data;
|
2005-03-14 17:28:31 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Base class for storage of Instance objects. Actually three
|
|
|
|
// instances are created: the main instance which keeps the
|
|
|
|
// desired value, an additional instance that keeps a reference
|
|
|
|
// to that value, and another instance that keeps a const
|
|
|
|
// reference to that value. These additional instances are queried
|
|
|
|
// when casting the Value to a reference type.
|
|
|
|
struct Instance_box_base
|
|
|
|
{
|
|
|
|
Instance_box_base()
|
|
|
|
: inst_(0),
|
2005-04-29 19:19:58 +08:00
|
|
|
_ref_inst(0),
|
|
|
|
_const_ref_inst(0)
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual ~Instance_box_base()
|
|
|
|
{
|
|
|
|
delete inst_;
|
2005-04-29 19:19:58 +08:00
|
|
|
delete _ref_inst;
|
|
|
|
delete _const_ref_inst;
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// clones the instance box
|
|
|
|
virtual Instance_box_base *clone() const = 0;
|
|
|
|
// returns the type of the value held
|
2005-04-29 19:19:58 +08:00
|
|
|
virtual const Type* type() const = 0;
|
2005-03-14 17:28:31 +08:00
|
|
|
// returns the actual pointed type if applicable
|
2005-04-29 19:19:58 +08:00
|
|
|
virtual const Type* ptype() const { return 0; }
|
2005-03-14 17:28:31 +08:00
|
|
|
// returns whether the data is a null pointer
|
|
|
|
virtual bool nullptr() const = 0;
|
|
|
|
|
|
|
|
Instance_base *inst_;
|
2005-04-29 19:19:58 +08:00
|
|
|
Instance_base *_ref_inst;
|
|
|
|
Instance_base *_const_ref_inst;
|
2005-03-14 17:28:31 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Generic instance box for non-pointer values.
|
|
|
|
template<typename T>
|
|
|
|
struct Instance_box: Instance_box_base
|
|
|
|
{
|
|
|
|
Instance_box(): Instance_box_base(), nullptr_(false) {}
|
|
|
|
|
|
|
|
Instance_box(const T &d, bool nullptr = false)
|
|
|
|
: Instance_box_base(),
|
|
|
|
nullptr_(nullptr)
|
|
|
|
{
|
|
|
|
Instance<T> *vl = new Instance<T>(d);
|
|
|
|
inst_ = vl;
|
2005-04-29 19:19:58 +08:00
|
|
|
_ref_inst = new Instance<T &>(vl->_data);
|
|
|
|
_const_ref_inst = new Instance<const T &>(vl->_data);
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual Instance_box_base *clone() const
|
|
|
|
{
|
|
|
|
Instance_box<T> *new_inbox = new Instance_box<T>();
|
|
|
|
|
|
|
|
// ??? this static_cast<> shouldn't be necessary, but the
|
|
|
|
// MSVC++ compiler complains about invalid casting without it!
|
|
|
|
Instance<T> *vl = static_cast<Instance<T> *>(inst_->clone());
|
|
|
|
|
|
|
|
new_inbox->inst_ = vl;
|
2005-04-29 19:19:58 +08:00
|
|
|
new_inbox->_ref_inst = new Instance<T &>(vl->_data);
|
|
|
|
new_inbox->_const_ref_inst = new Instance<const T &>(vl->_data);
|
2005-03-14 17:28:31 +08:00
|
|
|
new_inbox->nullptr_ = nullptr_;
|
|
|
|
return new_inbox;
|
|
|
|
}
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
virtual const Type* type() const
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2007-02-13 01:14:46 +08:00
|
|
|
return &typeof(T);
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool nullptr() const
|
|
|
|
{
|
|
|
|
return nullptr_;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool nullptr_;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Generic instance box for pointer values. Unlike Instance_box<>,
|
|
|
|
// this struct template provides a ptype() method that unreferences
|
|
|
|
// the pointer (T is supposed to be a pointer) and gets its actual
|
|
|
|
// type.
|
|
|
|
template<typename T>
|
|
|
|
struct Ptr_instance_box: Instance_box_base
|
|
|
|
{
|
|
|
|
Ptr_instance_box(): Instance_box_base() {}
|
|
|
|
|
|
|
|
Ptr_instance_box(const T &d)
|
|
|
|
: Instance_box_base()
|
|
|
|
{
|
|
|
|
Instance<T> *vl = new Instance<T>(d);
|
|
|
|
inst_ = vl;
|
2005-04-29 19:19:58 +08:00
|
|
|
_ref_inst = new Instance<T &>(vl->_data);
|
|
|
|
_const_ref_inst = new Instance<const T &>(vl->_data);
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual Instance_box_base *clone() const
|
|
|
|
{
|
|
|
|
Ptr_instance_box<T> *new_inbox = new Ptr_instance_box<T>();
|
|
|
|
|
|
|
|
// ??? this static_cast<> shouldn't be necessary, but the
|
|
|
|
// MSVC++ compiler complains about invalid casting without it!
|
|
|
|
Instance<T> *vl = static_cast<Instance<T> *>(inst_->clone());
|
|
|
|
|
|
|
|
new_inbox->inst_ = vl;
|
2005-04-29 19:19:58 +08:00
|
|
|
new_inbox->_ref_inst = new Instance<T &>(vl->_data);
|
|
|
|
new_inbox->_const_ref_inst = new Instance<const T &>(vl->_data);
|
2005-03-14 17:28:31 +08:00
|
|
|
return new_inbox;
|
|
|
|
}
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
virtual const Type* type() const
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2007-02-13 01:14:46 +08:00
|
|
|
return &typeof(T);
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
virtual const Type* ptype() const
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
if (!static_cast<Instance<T> *>(inst_)->_data) return 0;
|
2007-02-13 01:14:46 +08:00
|
|
|
return &typeof(typename remove_pointer<T>::type);
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool nullptr() const
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
return static_cast<Instance<T> *>(inst_)->_data == 0;
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
Instance_box_base *_inbox;
|
|
|
|
const Type* _type;
|
|
|
|
const Type* _ptype;
|
2005-03-14 17:28:31 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/// A vector of values.
|
|
|
|
typedef std::vector<Value> ValueList;
|
2005-04-04 21:50:07 +08:00
|
|
|
|
|
|
|
|
2005-03-14 17:28:31 +08:00
|
|
|
// INLINE METHODS
|
|
|
|
|
|
|
|
inline Value::Value()
|
2005-04-29 19:19:58 +08:00
|
|
|
: _inbox(0),
|
|
|
|
_type(&Reflection::type_void()),
|
|
|
|
_ptype(0)
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T> Value::Value(const T &v)
|
2005-04-29 19:19:58 +08:00
|
|
|
: _ptype(0)
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
_inbox = new Instance_box<T>(v);
|
|
|
|
_type = _inbox->type();
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
inline Value::Value(const void *v)
|
2005-04-29 19:19:58 +08:00
|
|
|
: _ptype(0)
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
_inbox = new Instance_box<const void *>(v, v == 0);
|
|
|
|
_type = _inbox->type();
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
inline Value::Value(void *v)
|
2005-04-29 19:19:58 +08:00
|
|
|
: _ptype(0)
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
_inbox = new Instance_box<void *>(v, v == 0);
|
|
|
|
_type = _inbox->type();
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T> Value::Value(const T *v)
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
_inbox = new Ptr_instance_box<const T *>(v);
|
|
|
|
_type = _inbox->type();
|
|
|
|
_ptype = _inbox->ptype();
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T> Value::Value(T *v)
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
_inbox = new Ptr_instance_box<T *>(v);
|
|
|
|
_type = _inbox->type();
|
|
|
|
_ptype = _inbox->ptype();
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
inline Value::Value(const Value& copy)
|
|
|
|
: _inbox(copy._inbox? copy._inbox->clone(): 0),
|
|
|
|
_type(copy._type),
|
|
|
|
_ptype(copy._ptype)
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
inline Value& Value::operator=(const Value& copy)
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
std::auto_ptr<Instance_box_base> new_inbox(copy._inbox? copy._inbox->clone(): 0);
|
|
|
|
delete _inbox;
|
|
|
|
_inbox = new_inbox.release();
|
|
|
|
_type = copy._type;
|
|
|
|
_ptype = copy._ptype;
|
2005-03-14 17:28:31 +08:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline Value::~Value()
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
delete _inbox;
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
inline const Type& Value::getType() const
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
return *_type;
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
2005-04-29 19:19:58 +08:00
|
|
|
inline const Type& Value::getInstanceType() const
|
2005-03-14 17:28:31 +08:00
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
if (_ptype)
|
|
|
|
return *_ptype;
|
|
|
|
return *_type;
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
inline bool Value::isTypedPointer() const
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
return _ptype != 0;
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
inline bool Value::isEmpty() const
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
return _inbox == 0;
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
inline bool Value::isNullPointer() const
|
|
|
|
{
|
2005-04-29 19:19:58 +08:00
|
|
|
return _inbox->nullptr();
|
2005-03-14 17:28:31 +08:00
|
|
|
}
|
2004-12-09 13:28:20 +08:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|