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