持久化的 map ,使用 BerkeleyDB

项目地址:http://code.google.com/p/febird

 

使用前面介绍的序列化框架,可以非常简单地将Bekeley DB作为存储层,实现一个易于使用的,强类型的,持久化的map。

 

这个设计的的基本原则就是:模板作为一个薄的、类型安全的包装层,实现层的代码可以多个模板实例来公用,这样不但加快了编译时间,也减小了生成的代码尺寸。

这个实现相当于std::map<Key,Data>,但接口上也不完全相同,主要是基于易实现和性能考虑。

下一篇介绍std::map<Key1,std::map<Key2,Data> >的BerkeleyDB实现。

 

 多的不说,贴上代码。

 

file: dbmap.h

 

 

 

  1. /* vim: set tabstop=4 : */
  2. #ifndef __febird_bdb_dbmap_h__
  3. #define __febird_bdb_dbmap_h__
  4. #if defined(_MSC_VER) && (_MSC_VER >= 1020)
  5. # pragma once
  6. #endif
  7. #include <db_cxx.h>
  8. #include "native_compare.h"
  9. #include "../io/DataIO.h"
  10. #include "../io/MemStream.h"
  11. #include "../refcount.h"
  12. namespace febird {
  13. class FEBIRD_DLL_EXPORT dbmap_iterator_impl_base : public RefCounter
  14. {
  15. public:
  16.     class dbmap_base* m_owner;
  17.     DBC*  m_curp;
  18.     int   m_ret;
  19. public:
  20.     dbmap_iterator_impl_base(class dbmap_base* owner);
  21.     void init(DB* dbp, DB_TXN* txn, const char* func);
  22.     virtual ~dbmap_iterator_impl_base();
  23.     virtual void load_key(void* data, size_t size) = 0;
  24.     virtual void load_val(void* data, size_t size) = 0;
  25.     virtual void save_key(PortableDataOutput<AutoGrownMemIO>& oKey) = 0;
  26.     void advance(u_int32_t direction_flag, const char* func);
  27.     void update(const void* d, const char* func);
  28.     void remove(const char* func);
  29. };
  30. class FEBIRD_DLL_EXPORT dbmap_base
  31. {
  32.     DECLARE_NONE_COPYABLE_CLASS(dbmap_base)
  33. public:
  34.     DB*    m_db;
  35.     size_t m_bulkSize;
  36.     bt_compare_fcn_type m_bt_comp;
  37.     dbmap_base(DB_ENV* env, const char* dbname
  38.         , DB_TXN* txn
  39.         , bt_compare_fcn_type bt_comp
  40.         , const char* func
  41.         );
  42.     virtual ~dbmap_base();
  43.     virtual void save_key(PortableDataOutput<AutoGrownMemIO>& dio, const void* key) const = 0;
  44.     virtual void save_val(PortableDataOutput<AutoGrownMemIO>& dio, const void* data) const = 0;
  45.     virtual dbmap_iterator_impl_base* make_iter() = 0;
  46.     dbmap_iterator_impl_base* begin_impl(DB_TXN* txn, const char* func);
  47.     dbmap_iterator_impl_base* end_impl(DB_TXN* txn, const char* func);
  48.     dbmap_iterator_impl_base* find_impl(const void* k, DB_TXN* txn, u_int32_t flags, const char* func);
  49.     bool insert_impl(const void* k, const void* d, u_int32_t flags, DB_TXN* txn, const char* func);
  50.     bool remove_impl(const void* k, DB_TXN* txn, const char* func);
  51.     void clear_impl(DB_TXN* txn, const char* func);
  52. };
  53. template<class Key, class Val, class Value, class Impl>
  54. class dbmap_iterator :
  55.     public std::iterator<std::bidirectional_iterator_tag, Value, ptrdiff_tconst Value*, const Value&>
  56. {
  57.     boost::intrusive_ptr<Impl> m_impl;
  58.     void copy_on_write()
  59.     {
  60.         if (m_impl->getRefCount() > 1)
  61.         {
  62.             Impl* p = new Impl(m_impl->m_owner);
  63.             m_impl->m_ret = m_impl->m_curp->dup(m_impl->m_curp, &p->m_curp, DB_POSITION);
  64.             FEBIRD_RT_assert(0 == m_impl->m_ret, std::runtime_error);
  65.             m_impl.reset(p);
  66.         }
  67.     }
  68. private:
  69. #ifdef _MSC_VER
  70. //# pragma warning(disable: 4661) // declaration but not definition
  71. //! MSVC will warning C4661 "declaration but not definition"
  72. void operator++(int) { assert(0); }
  73. void operator--(int) { assert(0); }
  74. #else
  75. //! disable, because clone iterator will cause very much time and resource
  76. void operator++(int);// { assert(0); }
  77. void operator--(int);// { assert(0); }
  78. #endif
  79. public:
  80.     dbmap_iterator() {}
  81.     explicit dbmap_iterator(dbmap_iterator_impl_base* impl)
  82.         : m_impl(static_cast<Impl*>(impl))
  83.     {
  84.         assert(impl);
  85.         assert(dynamic_cast<Impl*>(impl));
  86.     }
  87. //  bool exist() const { return DB_NOTFOUND != m_impl->m_ret && DB_KEYEMPTY != m_impl->m_ret; }
  88.     bool exist() const { return 0 == m_impl->m_ret; }
  89.     void update(const Val& val) { m_impl->update(&val, BOOST_CURRENT_FUNCTION); }
  90.     void remove() { m_impl->remove(BOOST_CURRENT_FUNCTION); }
  91.     dbmap_iterator& operator++()
  92.     {
  93.         assert(0 == m_impl->m_ret);
  94.         copy_on_write();
  95.         m_impl->advance(DB_NEXT, BOOST_CURRENT_FUNCTION);
  96.         return *this;
  97.     }
  98.     dbmap_iterator& operator--()
  99.     {
  100.         assert(0 == m_impl->m_ret);
  101.         copy_on_write();
  102.         m_impl->advance(DB_PREV, BOOST_CURRENT_FUNCTION);
  103.         return *this;
  104.     }
  105.     const Value& operator*() const
  106.     {
  107.         assert(0 == m_impl->m_ret);
  108.         return m_impl->m_val;
  109.     }
  110.     const Value* operator->() const
  111.     {
  112.         assert(0 == m_impl->m_ret);
  113.         return &m_impl->m_val;
  114.     }
  115.     Value& get_mutable() const
  116.     {
  117.         assert(0 == m_impl->m_ret);
  118.         return m_impl->m_val;
  119.     }
  120. };
  121. template<class Key, class Val>
  122. class dbmap : protected dbmap_base
  123. {
  124.     DECLARE_NONE_COPYABLE_CLASS(dbmap)
  125. public:
  126.     typedef Key     key_type;
  127.     typedef std::pair<Key, Val> value_type;
  128. protected:
  129.     class dbmap_iterator_impl : public dbmap_iterator_impl_base
  130.     {
  131.     public:
  132.         value_type m_val;
  133.         dbmap_iterator_impl(dbmap_base* owner)
  134.             : dbmap_iterator_impl_base(owner)
  135.         {}
  136.         virtual void load_key(void* data, size_t size)
  137.         {
  138.             PortableDataInput<MemIO> iKey;
  139.             iKey.set(data, size);
  140.             iKey >> m_val.first;
  141.             FEBIRD_RT_assert(iKey.eof(), std::logic_error);
  142.         }
  143.         virtual void load_val(void* data, size_t size)
  144.         {
  145.             PortableDataInput<MinMemIO> iVal;
  146.             iVal.set(data);
  147.             iVal >> m_val.second;
  148.             FEBIRD_RT_assert(iVal.diff(data) == size, std::logic_error);
  149.         }
  150.         virtual void save_key(PortableDataOutput<AutoGrownMemIO>& oKey1)
  151.         {
  152.             oKey1 << m_val.first;
  153.         }
  154.     };
  155.     //! overrides
  156.     void save_key(PortableDataOutput<AutoGrownMemIO>& dio, const void* key) const { dio << *(const Key*)key; }
  157.     void save_val(PortableDataOutput<AutoGrownMemIO>& dio, const void* val) const { dio << *(const Val*)val; }
  158.     dbmap_iterator_impl_base* make_iter() { return new dbmap_iterator_impl(this); }
  159. public:
  160.     typedef dbmap_iterator<Key, Val, value_type, dbmap_iterator_impl>
  161.             iterator, const_iterator;
  162.     dbmap(DB_ENV* env, const char* dbname
  163.         , DB_TXN* txn = NULL
  164.         , bt_compare_fcn_type bt_comp = bdb_auto_bt_compare((Key*)(0))
  165.         )
  166.         : dbmap_base(env, dbname, txn, bt_comp, BOOST_CURRENT_FUNCTION)
  167.     {
  168.     }
  169.     dbmap(DbEnv* env, const char* dbname
  170.         , DbTxn* txn = NULL
  171.         , bt_compare_fcn_type bt_comp = bdb_auto_bt_compare((Key*)(0))
  172.         )
  173.         : dbmap_base(env->get_DB_ENV(), dbname, txn ? txn->get_DB_TXN() : NULL, bt_comp, BOOST_CURRENT_FUNCTION)
  174.     {
  175.     }
  176.     iterator begin(DB_TXN* txn = NULL) { return iterator(begin_impl(txn, BOOST_CURRENT_FUNCTION)); }
  177.     iterator end  (DB_TXN* txn = NULL) { return iterator(end_impl  (txn, BOOST_CURRENT_FUNCTION)); }
  178.     iterator begin(DbTxn* txn) { return iterator(begin_impl(txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION)); }
  179.     iterator end  (DbTxn* txn) { return iterator(end_impl  (txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION)); }
  180.     value_type back()
  181.     {
  182.         iterator iter = this->end();
  183.         --iter;
  184.         if (iter.exist())
  185.             return *iter;
  186.         throw std::runtime_error(BOOST_CURRENT_FUNCTION);
  187.     }
  188.     value_type front()
  189.     {
  190.         iterator iter = this->begin();
  191.         if (iter.exist())
  192.             return *iter;
  193.         throw std::runtime_error(BOOST_CURRENT_FUNCTION);
  194.     }
  195.     iterator find(const Key& k, DB_TXN* txn = NULL)
  196.     {
  197.         return iterator(find_impl(&k, txn, DB_SET, BOOST_CURRENT_FUNCTION));
  198.     }
  199.     iterator find(const Key& k, DbTxn* txn)
  200.     {
  201.         return iterator(find_impl(&k, txn ? txn->get_DB_TXN() : NULL, DB_SET, BOOST_CURRENT_FUNCTION));
  202.     }
  203.     iterator lower_bound(const Key& k, DB_TXN* txn = NULL)
  204.     {
  205.         return iterator(find_impl(&k, txn, DB_SET_RANGE, BOOST_CURRENT_FUNCTION));
  206.     }
  207.     iterator lower_bound(const Key& k, DbTxn* txn)
  208.     {
  209.         return iterator(find_impl(&k, txn ? txn->get_DB_TXN() : NULL, DB_SET_RANGE, BOOST_CURRENT_FUNCTION));
  210.     }
  211.     bool insert(const std::pair<Key,Val>& kv, DB_TXN* txn = NULL)
  212.     {
  213.         return insert_impl(&kv.first, &kv.second, DB_NOOVERWRITE, txn, BOOST_CURRENT_FUNCTION);
  214.     }
  215.     bool insert(const std::pair<Key,Val>& kv, DbTxn* txn)
  216.     {
  217.         return insert_impl(&kv.first, &kv.second, DB_NOOVERWRITE, txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION);
  218.     }
  219.     bool insert(const Key& k, const Val& d, DB_TXN* txn = NULL)
  220.     {
  221.         return insert_impl(&k, &d, DB_NOOVERWRITE, txn, BOOST_CURRENT_FUNCTION);
  222.     }
  223.     bool insert(const Key& k, const Val& d, DbTxn* txn)
  224.     {
  225.         return insert_impl(&k, &d, DB_NOOVERWRITE, txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION);
  226.     }
  227.     void replace(const std::pair<Key,Val>& kv, DB_TXN* txn = NULL)
  228.     {
  229.         insert_impl(&kv.first, &kv.second, 0, txn, BOOST_CURRENT_FUNCTION);
  230.     }
  231.     void replace(const std::pair<Key,Val>& kv, DbTxn* txn)
  232.     {
  233.         insert_impl(&kv.first, &kv.second, 0, txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION);
  234.     }
  235.     void replace(const Key& k, const Val& d, DB_TXN* txn = NULL)
  236.     {
  237.         insert_impl(&k, &d, 0, txn, BOOST_CURRENT_FUNCTION);
  238.     }
  239.     void replace(const Key& k, const Val& d, DbTxn* txn)
  240.     {
  241.         insert_impl(&k, &d, 0, txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION);
  242.     }
  243.     bool remove(const Key& k, DB_TXN* txn = NULL)
  244.     {
  245.         return remove_impl(&k, txn, BOOST_CURRENT_FUNCTION);
  246.     }
  247.     bool remove(const Key& k, DbTxn* txn)
  248.     {
  249.         return remove_impl(&k, txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION);
  250.     }
  251.     bool erase(iterator& iter)
  252.     {
  253.         return iter.remove();
  254.     }
  255.     void clear(DB_TXN* txn = NULL)
  256.     {
  257.         clear_impl(txn, BOOST_CURRENT_FUNCTION);
  258.     }
  259.     void clear(DbTxn* txn)
  260.     {
  261.         return clear_impl(txn ? txn->get_DB_TXN() : NULL, BOOST_CURRENT_FUNCTION);
  262.     }
  263.     DB* getDB() { return m_db; }
  264.     const DB* getDB() const { return m_db; }
  265. };
  266. // namespace febird
  267. #endif // __febird_bdb_dbmap_h__

file: dbmap.cpp

 

  1. /* vim: set tabstop=4 : */
  2. #include "dbmap.h"
  3. #include <sstream>
  4. /**
  5.  @brief when iterate from end to begin
  6.  @code
  7.     for (dbmap<Key, Val>::iterator iter = dbm.end();;)
  8.     {
  9.         --iter;
  10.         if (!iter.exist()) break;
  11.         pair<Key,Val>& kv = *iter;
  12.     //  do some thing
  13.     }
  14.  @endcode
  15.  */
  16. namespace febird {
  17. dbmap_iterator_impl_base::dbmap_iterator_impl_base(class dbmap_base* owner)
  18.     : m_owner(owner)
  19.     , m_curp(0), m_ret(-1)
  20. {
  21. }
  22. void dbmap_iterator_impl_base::init(DB* dbp, DB_TXN* txn, const char* func)
  23. {
  24.     int ret = dbp->cursor(dbp, txn, &m_curp, 0);
  25.     if (0 != ret)
  26.     {
  27.         delete this;
  28.         std::ostringstream oss;
  29.         oss << db_strerror(ret) << "... at: " << func;
  30.         throw std::runtime_error(oss.str());
  31.     }
  32.     m_ret = 0;
  33. }
  34. dbmap_iterator_impl_base::~dbmap_iterator_impl_base()
  35. {
  36.     if (m_curp)
  37.         m_curp->close(m_curp);
  38. }
  39. void dbmap_iterator_impl_base::advance(u_int32_t direction_flag, const char* func)
  40. {
  41.     FEBIRD_RT_assert(0 == m_ret, std::logic_error);
  42.     DBT tk; memset(&tk, 0, sizeof(DBT));
  43.     DBT td; memset(&td, 0, sizeof(DBT));
  44.     m_ret = m_curp->get(m_curp, &tk, &td, direction_flag);
  45.     if (0 == m_ret)
  46.     {
  47.         load_key(tk.data, tk.size);
  48.         load_val(td.data, td.size);
  49.     }
  50.     else if (DB_NOTFOUND != m_ret)
  51.     {
  52.         std::ostringstream oss;
  53.         oss << db_strerror(m_ret) << "... at: " << func;
  54.         throw std::runtime_error(oss.str());
  55.     }
  56. }
  57. /**
  58.  @brief 
  59.  @return true successful updated
  60.         false (key, d.key2) did not exist, not updated
  61.  @throw other errors
  62.  */
  63. void dbmap_iterator_impl_base::update(const void* d, const char* func)
  64. {
  65.     FEBIRD_RT_assert(0 == m_ret, std::logic_error);
  66.     PortableDataOutput<AutoGrownMemIO> oKey1, oData;
  67.     this->save_key(oKey1);
  68.     m_owner->save_val(oData, d);
  69.     DBT tk; memset(&tk, 0, sizeof(DBT)); tk.data = oKey1.begin(); tk.size = oKey1.tell();
  70.     DBT td; memset(&td, 0, sizeof(DBT)); td.data = oData.begin(); td.size = oData.tell();
  71.     int ret = m_curp->put(m_curp, &tk, &td, DB_CURRENT);
  72.     if (0 != ret)
  73.     {
  74.         std::ostringstream oss;
  75.         oss << db_strerror(ret)
  76.             << "... at: " << func;
  77.         throw std::runtime_error(oss.str());
  78.     }
  79. }
  80. void dbmap_iterator_impl_base::remove(const char* func)
  81. {
  82.     FEBIRD_RT_assert(0 == m_ret, std::logic_error);
  83.     int ret = m_curp->del(m_curp, 0);
  84.     if (0 != ret)
  85.     {
  86.         std::ostringstream oss;
  87.         oss << db_strerror(ret)
  88.             << "... at: " << func;
  89.         throw std::runtime_error(oss.str());
  90.     }
  91. }
  92. //////////////////////////////////////////////////////////////////////////
  93. dbmap_base::dbmap_base(DB_ENV* env, const char* dbname
  94.     , DB_TXN* txn
  95.     , bt_compare_fcn_type bt_comp
  96.     , const char* func
  97.     )
  98.     : m_bt_comp(0)
  99. {
  100.     m_bulkSize = 512*1024;
  101.     m_db = 0;
  102.     int ret = db_create(&m_db, env, 0);
  103.     if (0 == ret)
  104.     {
  105.         if (bt_comp) {
  106.             m_bt_comp = bt_comp;
  107.             m_db->set_bt_compare(m_db, bt_comp);
  108.         }
  109.         m_db->app_private = (this);
  110.         int flags = env->open_flags & (DB_THREAD|DB_MULTIVERSION|DB_AUTO_COMMIT);
  111.         ret = m_db->open(m_db, txn, dbname, dbname, DB_BTREE, DB_CREATE|flags, 0);
  112.     }
  113.     if (0 != ret)
  114.     {
  115.         std::ostringstream oss;
  116.         oss << db_strerror(ret)
  117.             << "... at: " << func;
  118.         throw std::runtime_error(oss.str());
  119.     }
  120. }
  121. dbmap_base::~dbmap_base()
  122. {
  123.     if (m_db)
  124.         m_db->close(m_db, 0);
  125. }
  126. dbmap_iterator_impl_base* dbmap_base::begin_impl(DB_TXN* txn, const char* func)
  127. {
  128.     dbmap_iterator_impl_base* iter = make_iter();
  129.     iter->init(m_db, txn, func);
  130.     DBT tk; memset(&tk, 0, sizeof(DBT));
  131.     DBT td; memset(&td, 0, sizeof(DBT));
  132.     iter->m_ret = iter->m_curp->get(iter->m_curp, &tk, &td, DB_FIRST);
  133.     if (0 == iter->m_ret)
  134.     {
  135.         iter->load_key(tk.data, tk.size);
  136.         iter->load_val(td.data, td.size);
  137.     }
  138.     else if (DB_NOTFOUND != iter->m_ret)
  139.     {
  140.         std::ostringstream oss;
  141.         oss << db_strerror(iter->m_ret)
  142.             << "... at: " << func;
  143.         delete iter; iter = 0;
  144.         throw std::runtime_error(oss.str());
  145.     }
  146.     return iter;
  147. }
  148. dbmap_iterator_impl_base* dbmap_base::end_impl(DB_TXN* txn, const char* func)
  149. {
  150.     dbmap_iterator_impl_base* iter = make_iter();
  151.     iter->init(m_db, txn, func);
  152. //  iter->m_ret = DB_NOTFOUND;
  153.     return iter;
  154. }
  155. dbmap_iterator_impl_base* dbmap_base::find_impl(const void* k, DB_TXN* txn, u_int32_t flags, const char* func)
  156. {
  157.     PortableDataOutput<AutoGrownMemIO> oKey1;
  158.     save_key(oKey1, k);
  159.     DBT tk; memset(&tk, 0, sizeof(DBT)); tk.data = oKey1.begin(); tk.size = oKey1.tell();
  160.     DBT td; memset(&td, 0, sizeof(DBT));
  161.     dbmap_iterator_impl_base* iter = make_iter();
  162.     iter->init(m_db, txn, func);
  163.     iter->m_ret = iter->m_curp->get(iter->m_curp, &tk, &td, flags);
  164.     if (0 == iter->m_ret)
  165.     {
  166.         iter->load_key(tk.data, tk.size);
  167.         iter->load_val(td.data, td.size);
  168.     }
  169.     else if (DB_NOTFOUND != iter->m_ret && DB_KEYEMPTY != iter->m_ret)
  170.     {
  171.         std::ostringstream oss;
  172.         oss << db_strerror(iter->m_ret)
  173.             << "... at: " << func
  174.             << "\n"
  175.             << "flags=" << flags
  176.             ;
  177.         throw std::runtime_error(oss.str());
  178.     }
  179.     return iter;
  180. }
  181. /**
  182.  @brief insert a record
  183.  @return true success,  no same k-k2 in db, the record was inserted
  184.          false failed, has same k-k2 in db, the record was not inserted, not replaced existing yet
  185.  @throws exception, failed
  186.  */
  187. bool dbmap_base::insert_impl(const void* k, const void* d, u_int32_t flags, DB_TXN* txn, const char* func)
  188. {
  189.     PortableDataOutput<AutoGrownMemIO> oKey1, oData;
  190.     try {
  191.         save_key(oKey1, k);
  192.         save_val(oData, d);
  193.     }
  194.     catch (const IOException& exp)
  195.     {
  196.         std::ostringstream oss;
  197.         oss << exp.what() << "... at: " << func;
  198.         throw std::runtime_error(oss.str());
  199.     }
  200.     DBT tk; memset(&tk, 0, sizeof(DBT)); tk.data = oKey1.begin(); tk.size = oKey1.tell();
  201.     DBT td; memset(&td, 0, sizeof(DBT)); td.data = oData.begin(); td.size = oData.tell();
  202.     int ret = m_db->put(m_db, txn, &tk, &td, flags);
  203.     if (DB_KEYEXIST == ret)
  204.         return false;
  205.     if (0 == ret)
  206.         return true;
  207.     std::ostringstream oss;
  208.     oss << db_strerror(ret)
  209.         << "... at: " << func
  210.         << "\n"
  211.         ;
  212.     throw std::runtime_error(oss.str());
  213. }
  214. /**
  215.  @brief 
  216.  @return true (k) existed, remove success
  217.         false (k) not existed, nothing done
  218.  */
  219. bool dbmap_base::remove_impl(const void* k, DB_TXN* txn, const char* func)
  220. {
  221.     PortableDataOutput<AutoGrownMemIO> oKey1;
  222.     save_key(oKey1, k);
  223.     DBT tk; memset(&tk, 0, sizeof(DBT)); tk.data = oKey1.begin(); tk.size = oKey1.tell();
  224.     int ret = m_db->del(m_db, txn, &tk, 0);
  225.     if (DB_NOTFOUND == ret)
  226.         return false;
  227.     if (0 == ret)
  228.         return true;
  229.     std::ostringstream oss;
  230.     oss << db_strerror(ret)
  231.         << "... at: " << func
  232.         << "\n"
  233.         ;
  234.     throw std::runtime_error(oss.str());
  235. }
  236. void dbmap_base::clear_impl(DB_TXN* txn, const char* func)
  237. {
  238.     u_int32_t count;
  239.     int ret = m_db->truncate(m_db, txn, &count, 0);
  240.     if (0 != ret)
  241.     {
  242.         std::ostringstream oss;
  243.         oss << db_strerror(ret)
  244.             << "... at: " << func
  245.             << "\n"
  246.             ;
  247.         throw std::runtime_error(oss.str());
  248.     }
  249. }
  250. // namespace febird
  251. 项目地址:http://code.google.com/p/febird

     

你可能感兴趣的:(thread,框架,Google,vim)