#include "unitransactiongen.h" #include "uniconftree.h" #include "unilistiter.h" #include "wvmoniker.h" static IUniConfGen *creator(WvStringParm s, IObject *_obj) { IUniConfGen *base = wvcreate(s, _obj); if (base) return new UniTransactionGen(base); else return NULL; } static WvMoniker moniker("transaction", creator); /* This enum is a field of UniConfChangeTree. It indicates the type of change represented by a node in a UniConfChangeTree. */ enum changeMode { /* This indicates that "newvalue" is valid and that its value should be written to the underlying generator at commit time. This tree *might* have children, which must be applied. "newvalue" will be a non-null pointer to a non-null WvString. */ NEWVALUE, /* This indicates that "newtree" is valid (but possibly NULL) and that the underlying generator's corresponding subtree should be made identical at commit time. This tree will *not* have children (though newtree might). */ NEWTREE, /* This indicates that "was_null_or_empty" is valid and that the key in the underlying generator should be created at commit time if it does not already exist at commit time. This tree *will* have children, which must be applied, and at least one of which will be non-BLANK. "was_null_or_empty" will be the return value of the WvString negation operation on the last known value of the corresponding key in the underlying generator; it is necessary in order to filter callbacks in certain cases. */ NEWNODE, /* This indicates that none of the fields are valid and that nothing should be done for this tree. This tree *will* have children, which must be applied, but they will all have mode of NEWTREE with newtree == NULL. */ BLANK }; class UniConfChangeTree : public UniConfTree { public: changeMode mode; // This used to be a union, but it was causing memory errors that were // extremely difficult to track down. Some of this code might serve no // purpose without this being a union, but I'd rather have it still work // and not leak than break it. -- mrwise WvString newvalue; UniConfValueTree *newtree; bool was_null_or_empty; // Constructs a tree and links it to a parent. UniConfChangeTree(UniConfChangeTree *parent, const UniConfKey &key) : UniConfTree(parent, key), newtree(0) {} // Destroys a tree and everything it owns. ~UniConfChangeTree() { if (newtree) delete newtree; } }; // Constructed by UniTransactionGen::iterator() to iterate over a section that // is to be completely replaced by a particular UniConfValueTree. class GenStyleValueTreeIter : public UniConfGen::Iter { public: GenStyleValueTreeIter(UniConfValueTree *node) : i(*node) { // printf("GenStyleValueTreeIter\n"); } ~GenStyleValueTreeIter() { // printf("~GenStyleValueTreeIter\n"); } void rewind() { i.rewind(); } bool next() { return i.next(); } UniConfKey key() const { return i->key(); } WvString value() const { return i->value(); } private: UniConfValueTree::Iter i; }; // Constructed by UniTransactionGen::iterator() to iterate over a section that // is being modified but not replaced. We iterate first over all of the values // that we're changing, except those we're deleting, and second over all // existing values not iterated over in the first stage, except those we're // deleting. class GenStyleChangeTreeIter : public UniConfGen::Iter { public: GenStyleChangeTreeIter(UniConfChangeTree *_root, const UniConfKey &_section, IUniConfGen *_base) : root(_root), section(_section), base(_base), doing_i1(true), i1(*root), i2(base->iterator(section)) { // printf("GenStyleChangeTreeIter(%s)\n", WvString(section).cstr()); } ~GenStyleChangeTreeIter() { // printf("~GenStyleChangeTreeIter(%s)\n", WvString(section).cstr()); if (i2) delete i2; } void rewind() { i1.rewind(); doing_i1 = true; } bool next() { if (doing_i1) { for (;;) { if (i1.next()) { if (i1->mode == NEWVALUE || i1->mode == NEWNODE || (i1->mode == NEWTREE && i1->newtree)) return true; } else break; } doing_i1 = false; if (i2) i2->rewind(); } if (i2) { for (;;) { if (i2->next()) { UniConfChangeTree *node = root->findchild(i2->key()); if (!node || node->mode == BLANK) return true; } else break; } } return false; } UniConfKey key() const { if (doing_i1) return i1->key(); else if (i2) return i2->key(); else return UniConfKey(); } WvString value() const { if (doing_i1) { if (i1->mode == NEWVALUE) return i1->newvalue; else if (i1->mode == NEWTREE) return i1->newtree->value(); else // i.e. i1->mode == NEWNODE { WvString value(base->get(UniConfKey(section, i1->key()))); return !value ? WvString::empty : value; } } else { return i2->value(); } } private: UniConfChangeTree *root; UniConfKey section; IUniConfGen *base; bool doing_i1; UniConfChangeTree::Iter i1; UniConfGen::Iter *i2; }; UniTransactionGen::UniTransactionGen(IUniConfGen *_base) : root(NULL), base(_base) { base->add_callback(this, wv::bind(&UniTransactionGen::gencallback, this, _1, _2)); } UniTransactionGen::~UniTransactionGen() { base->del_callback(this); WVRELEASE(base); WVDELETE(root); } WvString UniTransactionGen::get(const UniConfKey &key) { UniConfChangeTree *node = root; for (int seg = 0;; node = node->findchild(key.segment(seg++))) { if (!node) // If we couldn't find the next node, then we aren't // changing the requested key, and so the value is whatever // it currently is. return base->get(key); else if (node->mode == NEWTREE) { // Else if the next node has mode of NEWTREE, then we're changing // the requested key to whatever its value is in the stored // tree. if (node->newtree) { UniConfValueTree *subnode = node->newtree->find( key.last(key.numsegments() - seg)); if (subnode) return subnode->value(); } return WvString::null; } else if (seg == key.numsegments()) { // Else if this is the last node, then figure out what the node // would do and return the appropriate value. (The node's mode // will be either NEWVALUE, NEWNODE, or BLANK.) if (node->mode == NEWVALUE) return node->newvalue; WvString value(base->get(key.first(seg))); return (node->mode == NEWNODE && !value) ? WvString::empty : value; } } } void UniTransactionGen::set(const UniConfKey &key, WvStringParm value) { hold_delta(); root = set_change(root, key, 0, value); unhold_delta(); } void UniTransactionGen::setv(const UniConfPairList &pairs) { hold_delta(); UniConfPairList::Iter i(pairs); for (i.rewind(); i.next(); ) root = set_change(root, i->key(), 0, i->value()); unhold_delta(); } void UniTransactionGen::commit() { if (root) { // Apply our changes to the inner generator. We can't optimise // away callbacks at this point, because we may get notified of // changes caused by our changes. hold_delta(); apply_changes(root, UniConfKey()); // make sure the inner generator also commits base->commit(); // save deleting the root till now so we can hide any // redundant notifications caused by the base->commit() delete root; root = NULL; unhold_delta(); } // no need to base->commit() if we know we haven't changed anything! } bool UniTransactionGen::refresh() { if (root) { hold_delta(); cancel_changes(root, UniConfKey()); delete root; root = NULL; unhold_delta(); // no need to base->commit() here, since the inner generator never // saw any changes } // must always base->refresh(), even if we didn't change anything return base->refresh(); } UniConfGen::Iter *UniTransactionGen::iterator(const UniConfKey &key) { UniConfChangeTree *node = root; for (int seg = 0;; node = node->findchild(key.segment(seg++))) { if (!node) // If we couldn't find the next node, then we aren't changing the // children of the requested key, so they're whatever they // currently are. return base->iterator(key); else if (node->mode == NEWTREE) { // Else if the next node has mode of NEWTREE, then we're changing // the children of the requested key to whatever they are in the // stored tree. if (node->newtree) { UniConfValueTree *subnode = node->newtree->find( key.last(key.numsegments() - seg)); if (subnode) { UniConfGen::Iter *i = new GenStyleValueTreeIter(subnode); UniListIter *i2 = new UniListIter(this); i2->autofill(i); delete i; return i2; } } return new UniConfGen::NullIter(); } else if (seg == key.numsegments()) { // Else if this is the last node, then iterate over its direct // children. UniConfGen::Iter *i = new GenStyleChangeTreeIter(node, key, base); UniListIter *i2 = new UniListIter(this); i2->autofill(i); delete i; return i2; } } } void UniTransactionGen::apply_values(UniConfValueTree *newcontents, const UniConfKey §ion) { base->set(section, newcontents->value()); UniConfGen::Iter *j = base->iterator(section); if (j) { for (j->rewind(); j->next();) { if (newcontents->findchild(j->key()) == NULL) // Delete all children of the current value in the // underlying generator that do not exist in our // replacement tree. base->set(UniConfKey(section, j->key()), WvString::null); } delete j; } // Repeat for each child in the replacement tree. UniConfValueTree::Iter i(*newcontents); for (i.rewind(); i.next();) apply_values(i.ptr(), UniConfKey(section, i->key())); } void UniTransactionGen::apply_changes(UniConfChangeTree *node, const UniConfKey §ion) { if (node->mode == NEWTREE) { // If the current change is a NEWTREE change, then replace the // tree in the underlying generator with the stored one. if (node->newtree == NULL) base->set(section, WvString::null); else apply_values(node->newtree, section); // Since such changes have no children, return immediately. return; } else if (node->mode == NEWVALUE) { // Else if the current change is a NEWVALUE change, ... base->set(section, node->newvalue); } else if (node->mode == NEWNODE) { // Else if the current change is a NEWNODE change, ... if (!base->exists(section)) // ... and the current value in the underlying generator doesn't // exist, then create it. base->set(section, WvString::empty); // Note: This *is* necessary. We can't ignore this change and have // the underlying generator handle it, because it's possible that // this NEWNODE was the result of a set() which was later deleted. } // Repeat for each child in the change tree. UniConfChangeTree::Iter i(*node); for (i.rewind(); i.next();) apply_changes(i.ptr(), UniConfKey(section, i->key())); } struct my_userdata { UniConfValueTree *node; const UniConfKey &key; }; void UniTransactionGen::deletion_visitor(const UniConfValueTree *node, void *userdata) { my_userdata *data = (my_userdata *)userdata; delta(UniConfKey(data->key, node->fullkey(data->node)), WvString::null); } // Mirror image of apply_values() that issues all of the callbacks associated // with discarding a replacement value tree. void UniTransactionGen::cancel_values(UniConfValueTree *newcontents, const UniConfKey §ion) { WvString value(base->get(section)); if (!newcontents || newcontents->value() != value) delta(section, value); if (newcontents) { UniConfValueTree::Iter i(*newcontents); for (i.rewind(); i.next();) { UniConfKey subkey(section, i->key()); if (!base->exists(subkey)) { my_userdata data = { i.ptr(), subkey }; i->visit(wv::bind(&UniTransactionGen::deletion_visitor, this, _1, _2), (void*)&data, false, true); } } } UniConfGen::Iter *i = base->iterator(section); if (i) { for (i->rewind(); i->next();) cancel_values(newcontents ? newcontents->findchild(i->key()) : NULL, UniConfKey(section, i->key())); delete i; } } // Mirror image of apply_changes() that issues all of the callbacks associated // with discarding a change tree. void UniTransactionGen::cancel_changes(UniConfChangeTree *node, const UniConfKey §ion) { if (node->mode == NEWTREE) { if (!base->exists(section)) { if (node->newtree != NULL) { my_userdata data = { node->newtree, section }; node->newtree->visit( wv::bind(&UniTransactionGen::deletion_visitor, this, _1, _2), (void *)&data, false, true); } } else cancel_values(node->newtree, section); return; } WvString value; if (node->mode != BLANK) value = base->get(section); if (node->mode == NEWVALUE && !value.isnull() && value != node->newvalue) delta(section, value); UniConfChangeTree::Iter i(*node); for (i.rewind(); i.next();) cancel_changes(i.ptr(), UniConfKey(section, i->key())); if (node->mode != BLANK && value.isnull()) delta(section, WvString::null); } void UniTransactionGen::gencallback(const UniConfKey &key, WvStringParm value) { UniConfChangeTree *node = root; for (int seg = 0;; node = node->findchild(key.segment(seg++))) { if (!node) // If we couldn't find the next node, then we aren't changing // the changed key or any of its children, and so a callback // should be made. break; else if (node->mode == NEWTREE) // Else if the next node has mode of NEWTREE, then we're changing // the changed key and all of its children to whatever their // values are in the stored tree, and so the callback should be // ignored. return; else if (seg == key.numsegments()) { // Else if this is the last node, then figure out what we // should do. if (node->mode == NEWVALUE) // If we're replacing this key's value, then we should // ignore the callback. return; else if (node->mode == NEWNODE) { // Else if we want to create this key, then use its // was_null_or_empty flag to figure out if we need // to issue a callback, and update it if necessary. if (node->was_null_or_empty && !value) return; node->was_null_or_empty = !value; if (value.isnull()) { delta(key, WvString::empty); return; } break; } else // i.e. node->mode == BLANK // Else if we're doing nothing to this key, then a // callback should be made. break; } } // Make a normal callback. delta(key, value); } // Create and return a UniConfValueTree containing the value 'value' for // the key given by the segments of 'key' at and after position 'seg', with // parent 'parent' and key given by the segment of 'key' at position seg-1 // (which is the "root" key if seg == 0). Issue callbacks as necessary using // all the segments of 'key'. UniConfValueTree *UniTransactionGen::create_value(UniConfValueTree *parent, const UniConfKey &key, int seg, WvStringParm value) { UniConfValueTree *tree = 0; for (; seg != key.numsegments();) { // Create any needed intermediate nodes, each with value equal to // the empty string. parent = new UniConfValueTree(parent, key.segment(seg-1), WvString::empty); delta(key.first(seg++), WvString::empty); if (!tree) tree = parent; } // Create the last node with the specified value. parent = new UniConfValueTree(parent, key.segment(seg-1), value); delta(key, value); if (!tree) tree = parent; return tree; } void UniTransactionGen::deletion_simulator(const UniConfKey &key) { UniConfGen::Iter *i = base->iterator(key); if (i) { for (i->rewind(); i->next();) deletion_simulator(UniConfKey(key, i->key())); delete i; } delta(key, WvString::null); } // Like create_value(), but make a UniConfChangeTree containing a *change* // to value 'value'. UniConfChangeTree *UniTransactionGen::create_change(UniConfChangeTree *parent, const UniConfKey &key, int seg, WvStringParm value) { // if the key has a trailing slash, this should be a no-op: we don't // want this to have any effect if ((key.hastrailingslash()) && !value.isnull()) return parent; UniConfChangeTree *tree = 0; for (; seg != key.numsegments(); seg++) { parent = new UniConfChangeTree(parent, key.segment(seg-1)); if (value.isnull()) // We don't do anything for intermediate nodes when deleting, ... parent->mode = BLANK; else { // ... but when set()'ing a non-null value, we want them to exist. parent->mode = NEWNODE; UniConfKey nodekey(key.first(seg)); WvString curr = base->get(nodekey); parent->was_null_or_empty = !curr; if (curr.isnull()) delta(nodekey, WvString::empty); } if (!tree) tree = parent; } parent = new UniConfChangeTree(parent, key.segment(seg-1)); // Create the last node with the specified change. if (value.isnull()) { parent->mode = NEWTREE; parent->newtree = 0; if (base->exists(key)) deletion_simulator(key); } else { parent->mode = NEWVALUE; parent->newvalue = WvString(value); if (base->get(key) != value) delta(key, value); } if (!tree) tree = parent; return tree; } // Modify an existing UniConfValueTree to incorporate the set() of a // particular value for a particular key. Return a possibly altered // pointer to the root of the tree. 'seg' and 'key' are used like they // are in create_value(), and callbacks are made similarly. UniConfValueTree *UniTransactionGen::set_value(UniConfValueTree *node, const UniConfKey &key, int seg, WvStringParm value) { // printf("set_value('%s', %d)\n", WvString(key).cstr(), value.isnull()); if (value.isnull()) { // Delete the key if it exists. if (node) { UniConfValueTree *subnode = node->find( key.last(key.numsegments() - seg)); if (subnode) { hold_delta(); my_userdata data = { subnode, key }; subnode->visit(wv::bind(&UniTransactionGen::deletion_visitor, this, _1, _2), (void *)&data, false, true); // printf("DELETE SUBNODE!\n"); delete subnode; unhold_delta(); return subnode == node ? NULL : node; } else return node; } else return NULL; } else { // Switch to create_value() if we ever can't find the next node. if (!node) return create_value(NULL, key, seg, value); UniConfValueTree *subnode = node; for (; seg != key.numsegments();) { UniConfKey segment(key.segment(seg++)); UniConfValueTree *child = subnode->findchild(segment); // Switch to create_value() if we ever can't find the next node. if (!child) { create_value(subnode, key, seg, value); return node; } else subnode = child; } // The node already existed and we've found it; set it. if (value != subnode->value()) { subnode->setvalue(value); delta(key, value); } return node; } } void UniTransactionGen::deletion_simulator2(const UniConfKey &key) { UniConfGen::Iter *i = this->iterator(key); if (i) { for (i->rewind(); i->next();) deletion_simulator2(UniConfKey(key, i->key())); delete i; } delta(key, WvString::null); } // Like set_value(), but, again, for UniConfChangeTrees instead. UniConfChangeTree *UniTransactionGen::set_change(UniConfChangeTree *node, const UniConfKey &key, int seg, WvStringParm value) { // printf("set_change(key=%s,mode=%d) = '%s'\n", // WvString(key).cstr(), node ? node->mode : 999, value.cstr()); // Switch to create_change() if we ever can't find the next node, // and switch to set_value() if we ever find a NEWTREE. if (!node) return create_change(NULL, key, seg, value); else if (node->mode == NEWTREE) { node->newtree = set_value(node->newtree, key, seg, value); return node; } UniConfChangeTree *subnode = node; for (; seg != key.numsegments();) { if (subnode->mode == BLANK && !value.isnull()) { // If we're setting a non-null value and we weren't previously // doing anything to this node, then now we want to create it. subnode->mode = NEWNODE; UniConfKey nodekey(key.first(seg)); WvString curr = base->get(nodekey); subnode->was_null_or_empty = !curr; if (curr.isnull()) delta(nodekey, WvString::empty); } UniConfKey segment(key.segment(seg++)); UniConfChangeTree *next = subnode->findchild(segment); // Switch to create_change() if we ever can't find the next node, // and switch to set_value() if we ever find a NEWTREE. if (!next) { create_change(subnode, key, seg, value); return node; } else if (next->mode == NEWTREE) { next->newtree = set_value(next->newtree, key, seg, value); return node; } else subnode = next; } // The node already existed, didn't have mode of NEWTREE, and we've // found it; change it. if (value.isnull()) { if (subnode->mode != BLANK || base->exists(key)) deletion_simulator2(key); subnode->zap(); subnode->mode = NEWTREE; subnode->newtree = 0; } else if (subnode->mode == NEWVALUE) { if (subnode->newvalue != value) { subnode->newvalue = value; delta(key, value); } } else if (subnode->mode == BLANK) { if (base->get(key) != value) delta(key, value); subnode->mode = NEWVALUE; subnode->newvalue = WvString(value); } else // i.e. subnode->mode == NEWNODE { WvString currval(base->get(key)); if ((!currval != !value) && (currval != value)) delta(key, value); subnode->mode = NEWVALUE; subnode->newvalue = WvString(value); } return node; } // We'll say we're okay whenever the underlying generator is. bool UniTransactionGen::isok() { return base->isok(); } void UniTransactionGen::flush_buffers() { }