File indexing completed on 2024-06-09 05:01:56
0001 /* 0002 SPDX-FileCopyrightText: 2007-2018 Thomas Baumgart <tbaumgart@kde.org> 0003 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include <stdint.h> 0008 #include <QMap> 0009 #include <QStack> 0010 #include <mymoneyexception.h> 0011 0012 #ifndef MYMONEYMAP_H 0013 #define MYMONEYMAP_H 0014 0015 #define MY_OWN_DEBUG 0 0016 0017 /** 0018 * @author Thomas Baumgart 0019 * 0020 * This template class adds transaction security to the QMap<> class. 0021 * The interface is very simple. Before you perform any changes, 0022 * you have to call the startTransaction() method. Then you can use 0023 * the insert(), modify() and remove() methods to modify the map. 0024 * Changes are recorded and if you are finished, use the 0025 * commitTransaction() to finish the transaction. If you want to go 0026 * back before you have committed the transaction, use 0027 * rollbackTransaction() to set the container to the state it was 0028 * in before you called startTransaction(). 0029 * 0030 * The implementation is based on the command pattern, in case 0031 * someone is interested. 0032 */ 0033 template <class Key, class T> 0034 class MyMoneyMap : protected QMap<Key, T> 0035 { 0036 private: 0037 // check if a key required (not already contained in the stack) or not 0038 bool required(const Key& key) const { 0039 if (m_stack.count() > 1) { 0040 for (auto i = 0; i < m_stack.count(); ++i) { 0041 if (m_stack[i]->key() == key) { 0042 return false; 0043 } 0044 } 0045 } 0046 return true; 0047 } 0048 0049 public: 0050 MyMoneyMap() : QMap<Key, T>() {} 0051 ~MyMoneyMap() {} 0052 0053 void startTransaction(unsigned long* id = 0) { 0054 m_stack.push(new MyMoneyMapStart(this, id)); 0055 } 0056 0057 void rollbackTransaction(void) { 0058 if (m_stack.isEmpty()) 0059 throw MYMONEYEXCEPTION_CSTRING("No transaction started to rollback changes"); 0060 0061 // undo all actions 0062 MyMoneyMapAction* action; 0063 while (!m_stack.isEmpty()) { 0064 action = m_stack.pop(); 0065 action->undo(); 0066 delete action; 0067 } 0068 } 0069 0070 bool commitTransaction(void) { 0071 if (m_stack.isEmpty()) 0072 throw MYMONEYEXCEPTION_CSTRING("No transaction started to commit changes"); 0073 0074 bool rc = m_stack.count() > 1; 0075 // remove all actions from the stack 0076 MyMoneyMapAction* action; 0077 while (!m_stack.isEmpty()) { 0078 action = m_stack.pop(); 0079 delete action; 0080 } 0081 return rc; 0082 } 0083 0084 void insert(const Key& key, const T& obj) { 0085 if (m_stack.isEmpty()) 0086 throw MYMONEYEXCEPTION_CSTRING("No transaction started to insert new element into container"); 0087 0088 // check if information about the object identified by 'key' 0089 // is already present in the stack 0090 if (!required(key)) { 0091 this->QMap<Key, T>::insert(key, obj); 0092 return; 0093 } 0094 0095 // store object in 0096 m_stack.push(new MyMoneyMapInsert(this, key, obj)); 0097 } 0098 0099 void modify(const Key& key, const T& obj) { 0100 if (m_stack.isEmpty()) 0101 throw MYMONEYEXCEPTION_CSTRING("No transaction started to modify element in container"); 0102 0103 #if 0 0104 // had to take this out, because we use QPair in one instance as key 0105 if (key.isEmpty()) 0106 throw MYMONEYEXCEPTION_CSTRING("No key to update object"); 0107 #endif 0108 0109 // check if information about the object identified by 'key' 0110 // is already present in the stack 0111 if (!required(key)) { 0112 this->QMap<Key, T>::insert(key, obj); 0113 return; 0114 } 0115 0116 m_stack.push(new MyMoneyMapModify(this, key, obj)); 0117 } 0118 0119 void remove(const Key& key) { 0120 if (m_stack.isEmpty()) 0121 throw MYMONEYEXCEPTION_CSTRING("No transaction started to remove element from container"); 0122 0123 #if 0 0124 // had to take this out, because we use QPair in one instance as key 0125 if (key.isEmpty()) 0126 throw MYMONEYEXCEPTION_CSTRING("No key to remove object"); 0127 #endif 0128 0129 // check if information about the object identified by 'key' 0130 // is already present in the stack 0131 if (!required(key)) { 0132 this->QMap<Key, T>::remove(key); 0133 return; 0134 } 0135 0136 m_stack.push(new MyMoneyMapRemove(this, key)); 0137 } 0138 0139 MyMoneyMap<Key, T>& operator= (const QMap<Key, T>& m) { 0140 if (!m_stack.isEmpty()) { 0141 throw MYMONEYEXCEPTION_CSTRING("Cannot assign whole container during transaction"); 0142 } 0143 QMap<Key, T>::operator=(m); 0144 return *this; 0145 } 0146 0147 0148 inline QList<T> values(void) const { 0149 return QMap<Key, T>::values(); 0150 } 0151 0152 inline QList<Key> keys(void) const { 0153 return QMap<Key, T>::keys(); 0154 } 0155 0156 const T& operator[](const Key& k) const { 0157 return find(k).value(); 0158 #if 0 0159 /*QT_CHECK_INVALID_MAP_ELEMENT;*/ /*PORT ME KDE4*/ return QMap<Key, T>::operator[](k); 0160 #endif 0161 } 0162 0163 inline typename QMap<Key, T>::const_iterator find(const Key& k) const { 0164 return QMap<Key, T>::find(k); 0165 } 0166 0167 inline typename QMap<Key, T>::const_iterator begin(void) const { 0168 return QMap<Key, T>::constBegin(); 0169 } 0170 0171 inline typename QMap<Key, T>::const_iterator end(void) const { 0172 return QMap<Key, T>::constEnd(); 0173 } 0174 0175 typedef typename QMap<Key, T>::const_iterator const_iterator; 0176 0177 inline bool contains(const Key& k) const { 0178 return find(k) != end(); 0179 } 0180 0181 inline void map(QMap<Key, T>& that) const { 0182 //QMap<Key, T>* ptr = dynamic_cast<QMap<Key, T>* >(this); 0183 //that = *ptr; 0184 that = *(static_cast<QMap<Key, T>* >(const_cast<MyMoneyMap<Key, T>* >(this))); 0185 } 0186 0187 inline int count(void) const { 0188 return QMap<Key, T>::count(); 0189 } 0190 0191 #if MY_OWN_DEBUG 0192 void dump(void) const { 0193 printf("Container dump\n"); 0194 printf(" items in container = %d\n", count()); 0195 printf(" items on stack = %d\n", m_stack.count()); 0196 0197 const_iterator it; 0198 for (it = begin(); it != end(); ++it) { 0199 printf(" %s \n", it.key().data()); 0200 } 0201 } 0202 #endif 0203 0204 private: 0205 class MyMoneyMapAction 0206 { 0207 public: 0208 MyMoneyMapAction(MyMoneyMap<Key, T>* container) : 0209 m_container(container) {} 0210 0211 MyMoneyMapAction(MyMoneyMap<Key, T>* container, const Key& key, const T& obj) : 0212 m_container(container), 0213 m_obj(obj), 0214 m_key(key) {} 0215 0216 virtual ~MyMoneyMapAction() {} 0217 virtual void undo(void) = 0; 0218 const Key& key(void) const { 0219 return m_key; 0220 } 0221 0222 protected: 0223 MyMoneyMap<Key, T>* m_container; 0224 T m_obj; 0225 Key m_key; 0226 }; 0227 0228 class MyMoneyMapStart : public MyMoneyMapAction 0229 { 0230 public: 0231 MyMoneyMapStart(MyMoneyMap<Key, T>* container, unsigned long* id) : 0232 MyMoneyMapAction(container), 0233 m_idPtr(id), 0234 m_id(0) { 0235 if (id != 0) 0236 m_id = *id; 0237 } 0238 ~MyMoneyMapStart() 0239 { 0240 } 0241 void undo(void) final override { 0242 if (m_idPtr != 0) 0243 *m_idPtr = m_id; 0244 } 0245 0246 private: 0247 unsigned long* m_idPtr; 0248 unsigned long m_id; 0249 }; 0250 0251 class MyMoneyMapInsert : public MyMoneyMapAction 0252 { 0253 public: 0254 MyMoneyMapInsert(MyMoneyMap<Key, T>* container, const Key& key, const T& obj) : 0255 MyMoneyMapAction(container, key, obj) { 0256 container->QMap<Key, T>::insert(key, obj); 0257 } 0258 0259 ~MyMoneyMapInsert() 0260 { 0261 } 0262 void undo(void) final override { 0263 // m_container->remove(m_key) does not work on GCC 4.0.2 0264 // using this-> to access those member does the trick 0265 this->m_container->QMap<Key, T>::remove(this->m_key); 0266 } 0267 }; 0268 0269 class MyMoneyMapRemove : public MyMoneyMapAction 0270 { 0271 public: 0272 MyMoneyMapRemove(MyMoneyMap<Key, T>* container, const Key& key) : 0273 MyMoneyMapAction(container, key, (*container)[key]) { 0274 container->QMap<Key, T>::remove(key); 0275 } 0276 0277 ~MyMoneyMapRemove() 0278 { 0279 } 0280 void undo(void) final override { 0281 this->m_container->insert(this->m_key, this->m_obj); 0282 } 0283 }; 0284 0285 class MyMoneyMapModify : public MyMoneyMapAction 0286 { 0287 public: 0288 MyMoneyMapModify(MyMoneyMap<Key, T>* container, const Key& key, const T& obj) : 0289 MyMoneyMapAction(container, key, (*container)[key]) { 0290 container->QMap<Key, T>::insert(key, obj); 0291 } 0292 0293 ~MyMoneyMapModify() 0294 { 0295 } 0296 void undo(void) final override { 0297 this->m_container->QMap<Key, T>::insert(this->m_key, this->m_obj); 0298 } 0299 }; 0300 0301 protected: 0302 QStack<MyMoneyMapAction *> m_stack; 0303 }; 0304 0305 #if MY_OWN_DEBUG 0306 #include <mymoneyaccount.h> 0307 #include <mymoneytransaction.h> 0308 main() 0309 { 0310 MyMoneyMap<QString, MyMoneyAccount> container; 0311 MyMoneyMap<QString, MyMoneyTransaction> ct; 0312 0313 MyMoneyAccount acc; 0314 acc.setName("Test"); 0315 // this should not be possible 0316 // container["a"] = acc; 0317 0318 QList<MyMoneyAccount> list; 0319 list = container.values(); 0320 0321 MyMoneyAccount b; 0322 b.setName("Thomas"); 0323 0324 try { 0325 container.startTransaction(); 0326 container.insert("001", acc); 0327 container.dump(); 0328 container.commitTransaction(); 0329 acc.setName("123"); 0330 container.startTransaction(); 0331 container.modify("001", acc); 0332 container.dump(); 0333 container.rollbackTransaction(); 0334 container.dump(); 0335 0336 container.startTransaction(); 0337 container.remove(QString("001")); 0338 container.dump(); 0339 container.rollbackTransaction(); 0340 container.dump(); 0341 0342 b = container["001"]; 0343 printf("b.name() = %s\n", b.name().data()); 0344 0345 QMap<QString, MyMoneyAccount>::ConstIterator it; 0346 it = container.find("001"); 0347 it = container.begin(); 0348 0349 } catch (const MyMoneyException &e) { 0350 printf("Caught exception: %s\n", e.what().data()); 0351 } 0352 0353 QMap<QString, MyMoneyAccount> map; 0354 map["005"] = b; 0355 container = map; 0356 0357 printf("b.name() = %s\n", container["001"].name().data()); 0358 printf("b.name() = %s\n", container["005"].name().data()); 0359 } 0360 0361 #endif 0362 0363 #endif