diff --git a/src/lib/diff/tree-mutator-attribute-binding.hpp b/src/lib/diff/tree-mutator-attribute-binding.hpp new file mode 100644 index 000000000..2c2528c3a --- /dev/null +++ b/src/lib/diff/tree-mutator-attribute-binding.hpp @@ -0,0 +1,738 @@ +/* + TREE-MUTATOR-ATTRIBUTE-BINDING.hpp - diff::TreeMutator implementation building block + + Copyright (C) Lumiera.org + 2016, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program 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 + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file tree-mutator-attribute-binding.hpp + ** Special binding implementation for TreeMutator, allowing to map + ** tree diff operations onto native object attributes. TreeMutator is a + ** customisable intermediary, which enables otherwise opaque implementation + ** data structures to receive and respond to generic structural change messages + ** ("tree diff"). + ** + ** Each concrete TreeMutator instance will be configured differently, and this + ** adaptation is done by implementing binding templates, in the way of building + ** blocks, attached and customised through lambdas. It is possible to layer + ** several bindings on top of a single TreeMutator -- and this header defines + ** a building block for one such layer, especially for binding to object fields + ** through getter / setter lambdas. + ** + ** @note the header tree-mutator-attribute-binding.hpp with specific builder templates + ** is included way down, after the class definitions. This is done so for sake + ** of readability. + ** + ** @see tree-mutator-test.cpp + ** @see TreeMutator::build() + ** + */ + + +#ifndef LIB_DIFF_TREE_MUTATOR_ATTRIBUTE_BINDING_H +#define LIB_DIFF_TREE_MUTATOR_ATTRIBUTE_BINDING_H +#ifndef LIB_DIFF_TREE_MUTATOR_H + #error "this header shall not be used standalone (see tree-mutator.hpp)" +#endif + + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/meta/trait.hpp" +#include "lib/diff/gen-node.hpp" +#include "lib/opaque-holder.hpp" +//#include "lib/util.hpp" +//#include "lib/format-string.hpp" + +#include +#include ////TODO +#include +//#include +//#include + + +namespace lib { + /////////////////////////////TODO move over into opaque-holder.hpp + /** + * handle to allow for safe _»remote implantation«_ + * of an unknown subclass into a given OpaqueHolder buffer, + * without having to disclose the concrete buffer type or size. + * @remarks this is especially geared towards use in APIs, allowing + * a not yet known implementation to implant an agent or collaboration + * partner into the likewise undisclosed innards of the exposed service. + * @warning the type BA must expose a virtual dtor, since the targetted + * OpaqueHolder has to take ownership of the implanted object. + */ + template + class PlantingHandle + { + void* buffer_; + size_t maxSiz_; + + ///////TODO static assert to virtual dtor?? + public: + template + PlantingHandle (InPlaceBuffer& targetBuffer) + : buffer_(&targetBuffer) + , maxSiz_(maxSiz) + { } + + + template + BA& + create (SUB&& subMutator) + { + if (sizeof(SUB) > maxSiz_) + throw error::Fatal("Unable to implant implementation object of size " + "exceeding the pre-established storage buffer capacity." + ,error::LUMIERA_ERROR_CAPACITY); + + using Holder = InPlaceBuffer; + Holder& holder = *static_cast (buffer_); + + return holder.create (std::forward (subMutator)); + } + + template + bool + canCreate() const + { + return sizeof(SUB) <= maxSiz_; + } + }; + /////////////////////////////TODO move over into opaque-holder.hpp +namespace diff{ + + namespace error = lumiera::error; + +//using util::_Fmt; + using lib::Literal; + using std::function; + using std::string; + + + + + class TestMutationTarget; // for unit testing + + + namespace { + template + struct Builder; + + using ID = Literal; + using Attribute = DataCap; + } + + + /** + * Customisable intermediary to abstract mutating operations + * on arbitrary, hierarchical object-like data. + * The TreeMutator exposes two distinct interfaces + * - the \em operation API -- similar to what a container exposes -- + * is the entirety of abstract operations that can be done to the + * subsumed, tree like target structure + * - the \em binding API allows to link some or all of these generic + * activities to concrete manipulations known within target scope. + */ + class TreeMutator + { + + public: + + /* ==== operation API ==== */ + + virtual bool + emptySrc () + { + return true; + // do nothing by default + } + + /** skip next src element and advance abstract source position */ + virtual void + skipSrc () + { + // do nothing by default + } + + /** establish new element at current position */ + virtual void + injectNew (GenNode const&) + { + // do nothing by default + } + + /** ensure the next source element matches with given spec */ + virtual bool + matchSrc (GenNode const&) + { + // do nothing by default + return false; + } + + /** accept existing element, when matching the given spec */ + virtual bool + acceptSrc (GenNode const&) + { + // do nothing by default + return false; + } + + /** repeatedly accept, until after the designated location */ + virtual bool + accept_until (GenNode const&) + { + // do nothing by default + return false; + } + + /** locate designated element and accept it at current position */ + virtual bool + findSrc (GenNode const&) + { + // do nothing by default + return false; + } + + /** locate the designated target element + * (must be already accepted into the target sequence). + * Perform an assignement with the given payload value + * @throw when assignement fails (typically error::Logic) + * @return false when unable to locate the target */ + virtual bool + assignElm (GenNode const&) + { + // do nothing by default + return false; + } + + + using MutatorBuffer = PlantingHandle; + + /** locate the designated target element + * and build a suittable sub-mutator for this element + * into the provided target buffer + * @throw error::Fatal when buffer is insufficient + * @return false when unable to locate the target */ + virtual bool + mutateChild (GenNode const&, MutatorBuffer) + { + // do nothing by default + return false; + } + + virtual void setAttribute (ID, Attribute&) { /* do nothing by default */ } + + /** + * start building a custom adapted tree mutator, + * where the operations are tied by closures or + * wrappers into the current implementation context. + */ + static Builder build(); + }; + + + namespace { // Mutator-Builder decorator components... + + using lib::meta::Strip; + + /** + * Type rebinding helper to pick up the actual argument type. + * Works both for functors and for lambda expressions + * @remarks Solution proposed 10/2011 by \link http://stackoverflow.com/users/224671/kennytm user "kennytm" \endlink + * in this \link http://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda/7943765#7943765 + * answer on stackoverflow \endlink + */ + template + struct _ClosureType + : _ClosureType + { }; + + template + struct _ClosureType + { + typedef ARG ArgType; + typedef RET ReturnType; + }; + + + template + struct ChangeOperation + : PAR + { + ID attribID_; + CLO change_; + + virtual void + setAttribute (ID id, Attribute& newValue) + { + using ValueType = typename _ClosureType::ArgType; + + if (id == attribID_) + change_(newValue.get()); + + else // delegate to other closures (Decorator-style) + PAR::setAttribute(id, newValue); + } + + ChangeOperation(ID id, CLO clo, PAR const& chain) + : PAR(chain) + , attribID_(id) + , change_(clo) + { } + }; + + + /** + * Attach to collection: Concrete binding setup. + * This record holds all the actual binding and closures + * used to attach the tree mutator to an external pre-existing + * STL container with child elements/objects. It serves as flexible + * connection, configuration and adaptation element, and will be embedded + * as a whole into the (\ref ChildCollectionMutator), which in turn implements + * the `TreeMutator` interface. The resulting compound is able to consume + * tree diff messages and apply the respective changes and mutations to + * an otherwise opaque implementation data structure. + * + * @tparam COLL a STL compliant collection type holding "child elements" + * @tparam MAT a closure to determine if a child matches a diff spec (GenNode) + * @tparam CTR a closure to construct a new child element from a given diff spec + * @tparam SEL predicate to determine if this binding layer has to process a diff message + * @tparam ASS a closure to assign / set a new value from a given diff spec + * @tparam MUT a closure to construct a nested mutator for some child element + */ + template + struct CollectionBinding + { + using Coll = typename Strip::TypeReferred; + using Elm = typename Coll::value_type; + + Coll& collection; + + SEL isApplicable; + MAT matches; + CTR construct; + ASS assign; + MUT openSub; + + + /* === content manipulation API === */ + + Coll contentBuffer; + +#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + void + initMutation (string mutatorID) + { + prev_content_.clear(); + swap (content_, prev_content_); + log_.event ("attachMutator "+mutatorID); + } + + void + inject (GenNode&& elm, string operationID) + { + content_.emplace_back (forward(elm)); + log_.event (operationID, renderNode (content_.back())); + } + + static iterator + search (GenNode::ID const& targetID, iterator pos) + { + while (pos and not pos->matches(targetID)) + ++pos; + return pos; + } + + iterator + locate (GenNode::ID const& targetID) + { + if (!empty() and content_.back().matches(targetID)) + return lastElm(); + else + return search (targetID, eachElm(content_)); + } +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + }; + + + /** + * Attach to collection: Building block for a concrete `TreeMutator`. + * This decorator will be outfitted with actual binding and closures + * and then layered on top of the (\ref TreeMutaor) base. The resulting + * compound is able to consume tree diff messages and apply the respective + * changes and mutations to an otherwise opaque implementation data structure. + * @remarks in practice, this is the most relevant and typical `TreeMutator` setup. + */ + template + class ChildCollectionMutator + : public PAR + { + BIN binding_; + + public: + ChildCollectionMutator(BIN wiringClosures, PAR const& chain) + : PAR(chain) + , binding_(wiringClosures) + { } + + + + /* ==== re-Implementation of the operation API ==== */ + +#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + /** skip next recorded src element + * @remarks TestWireTap adapter together with TestMutationTarget + * maintain a "shaddow copy" of the data and apply the detected diff + * against this internal copy. This allows to verify what's going on + */ + virtual void + skipSrc () override + { + if (pos_) + { + GenNode const& skippedElm = *pos_; + ++pos_; + target_.logSkip (skippedElm); + } + PAR::skipSrc(); + } + + /** record in the test taget + * that a new child element is + * being insertet at current position + */ + virtual void + injectNew (GenNode const& n) override + { + target_.inject (GenNode{n}, "injectNew"); + PAR::injectNew (n); + } + + virtual bool + emptySrc () override + { + return !pos_ + and PAR::emptySrc(); + } + + /** ensure the next recorded source element + * matches on a formal level with given spec */ + virtual bool + matchSrc (GenNode const& n) override + { + return PAR::matchSrc(n) + or pos_? n.matches(*pos_) + : false; + } + + /** accept existing element, when matching the given spec */ + virtual bool + acceptSrc (GenNode const& n) override + { + bool isSrcMatch = TestWireTap::matchSrc(n); + if (isSrcMatch) // NOTE: important to call our own method here, not the virtual function + { + target_.inject (move(*pos_), "acceptSrc"); + ++pos_; + } + return PAR::acceptSrc(n) + or isSrcMatch; + } + + /** locate designated element and accept it at current position */ + virtual bool + findSrc (GenNode const& ref) override + { + Iter found = TestMutationTarget::search (ref.idi, pos_); + if (found) + { + target_.inject (move(*found), "findSrc"); + } + return PAR::findSrc(ref) + or found; + } + + /** repeatedly accept, until after the designated location */ + virtual bool + accept_until (GenNode const& spec) + { + bool foundTarget = true; + + if (spec.matches (Ref::END)) + for ( ; pos_; ++pos_) + target_.inject (move(*pos_), "accept_until END"); + else + { + string logMsg{"accept_until "+spec.idi.getSym()}; + while (pos_ and not TestWireTap::matchSrc(spec)) + { + target_.inject (move(*pos_), logMsg); + ++pos_; + } + if (TestWireTap::matchSrc(spec)) + { + target_.inject (move(*pos_), logMsg); + ++pos_; + } + else + foundTarget = false; + } + return PAR::accept_until(spec) + or foundTarget; + } + + /** locate element already accepted into the target sequence + * and assign the designated payload value to it. */ + virtual bool + assignElm (GenNode const& spec) + { + Iter targetElm = target_.locate (spec.idi); + if (targetElm) + { + string logOldPayload{render(targetElm->data)}; + *targetElm = spec; + target_.logAssignment (*targetElm, logOldPayload); + } + return PAR::assignElm(spec) + or targetElm; + } + + /** locate the designated target element and build a suitable + * sub-mutator for this element into the provided target buffer */ + virtual bool + mutateChild (GenNode const& spec, TreeMutator::MutatorBuffer targetBuff) + { + if (PAR::mutateChild (spec, targetBuff)) + return true; + else // Test mode only -- + { // no other layer was able to provide a mutator + Iter targetElm = target_.locate (spec.idi); + if (targetElm) + { + targetBuff.create (TreeMutator::build()); + target_.logMutation (*targetElm); + return true; + } + return false; + } + } +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + }; + + + /** + * Nested DSL to define the specifics of a collection binding. + */ + template + struct CollectionBindingBuilder + : CollectionBinding + { + + template + CollectionBindingBuilder + matchElement(FUN matcher) + { + return { this->collection + , matcher + , this->construct + , this->isApplicable + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + constructFrom(FUN constructor) + { + return { this->collection + , this->matches + , constructor + , this->isApplicable + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + isApplicableIf(FUN selector) + { + return { this->collection + , this->matches + , this->construct + , selector + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + assignElement(FUN setter) + { + return { this->collection + , this->matches + , this->construct + , this->isApplicable + , setter + , this->openSub + }; + } + + template + CollectionBindingBuilder + buildChildMutator(FUN childMutationBuilder) + { + return { this->collection + , this->matches + , this->construct + , this->isApplicable + , this->assign + , childMutationBuilder + }; + } + }; + + template + struct _DefaultBinding + { + using Coll = typename Strip::TypeReferred; + using Elm = typename Coll::value_type; + + static bool + disable_selector (GenNode const& spec) + { + UNIMPLEMENTED ("dont discriminate by default"); + } + + static bool + default_contantMatch (Elm const& elm) + { + UNIMPLEMENTED ("fallback matcher"); + } + + static Elm + default_construct_from_payload (GenNode const& spec) + { + UNIMPLEMENTED ("default construct from spec payload"); + } + + static bool + disable_assignment (GenNode const& spec) + { + UNIMPLEMENTED ("disabled assignment"); + } + + static bool + disable_childMutation (GenNode const& spec, TreeMutator::MutatorBuffer targetBuff) + { + UNIMPLEMENTED ("inactive mutator builder"); + } + + + using FallbackBindingConfiguration + = CollectionBindingBuilder; + + static FallbackBindingConfiguration + attachTo (Coll& coll) + { + return { coll + , disable_selector + , default_contantMatch + , default_construct_from_payload + , disable_assignment + , disable_childMutation + }; + } + }; + + + template + auto + collection (COLL& coll) -> decltype(_DefaultBinding::attachTo(coll)) + { + return _DefaultBinding::attachTo(coll); + } + + + + + template + struct TestWireTap; + + + template + struct Builder + : PAR + { + Builder(PAR par) + : PAR(par) + { } + + template + using Change = ChangeOperation; + + template + using Collection = ChildCollectionMutator; + + using WireTap = TestWireTap; + + + /* ==== binding API ==== */ + + template + Builder> + change (Literal attributeID, CLO closure) + { + return Change (attributeID, closure, *this); + } + + template + Builder> + attach (BIN&& collectionBindingSetup) + { + return Collection (std::forward(collectionBindingSetup)); + } + + Builder + attachDummy (TestMutationTarget& dummy); + + }; + + }//(END) Mutator-Builder decorator components... + + + Builder + TreeMutator::build () + { + return TreeMutator(); + } + + +}} // namespace lib::diff +#endif /*LIB_DIFF_TREE_MUTATOR_ATTRIBUTE_BINDING_H*/ diff --git a/src/lib/diff/tree-mutator-collection-binding.hpp b/src/lib/diff/tree-mutator-collection-binding.hpp new file mode 100644 index 000000000..ce4ad3564 --- /dev/null +++ b/src/lib/diff/tree-mutator-collection-binding.hpp @@ -0,0 +1,738 @@ +/* + TREE-MUTATOR-COLLECTION-BINDING.hpp - diff::TreeMutator implementation building block + + Copyright (C) Lumiera.org + 2016, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program 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 + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file tree-mutator-collection-binding.hpp + ** Special binding implementation for TreeMutator, allowing to map + ** tree diff operations onto a STL collection of native implementation objects. + ** TreeMutator is a customisable intermediary, which enables otherwise opaque + ** implementation data structures to receive and respond to generic structural + ** change messages ("tree diff"). + ** + ** Each concrete TreeMutator instance will be configured differently, and this + ** adaptation is done by implementing binding templates, in the way of building + ** blocks, attached and customised through lambdas. It is possible to layer + ** several bindings on top of a single TreeMutator -- and this header defines + ** a building block for one such layer, especially for binding to a representation + ** of "child objects" managed within a typical STL container. + ** + ** @note the header tree-mutator-collection-binding.hpp with specific builder templates + ** is included way down, after the class definitions. This is done so for sake + ** of readability. + ** + ** @see tree-mutator-test.cpp + ** @see TreeMutator::build() + ** + */ + + +#ifndef LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H +#define LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H +#ifndef LIB_DIFF_TREE_MUTATOR_H + #error "this header shall not be used standalone (see tree-mutator.hpp)" +#endif + + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/meta/trait.hpp" +#include "lib/diff/gen-node.hpp" +#include "lib/opaque-holder.hpp" +//#include "lib/util.hpp" +//#include "lib/format-string.hpp" + +#include +#include ////TODO +#include +//#include +//#include + + +namespace lib { + /////////////////////////////TODO move over into opaque-holder.hpp + /** + * handle to allow for safe _»remote implantation«_ + * of an unknown subclass into a given OpaqueHolder buffer, + * without having to disclose the concrete buffer type or size. + * @remarks this is especially geared towards use in APIs, allowing + * a not yet known implementation to implant an agent or collaboration + * partner into the likewise undisclosed innards of the exposed service. + * @warning the type BA must expose a virtual dtor, since the targetted + * OpaqueHolder has to take ownership of the implanted object. + */ + template + class PlantingHandle + { + void* buffer_; + size_t maxSiz_; + + ///////TODO static assert to virtual dtor?? + public: + template + PlantingHandle (InPlaceBuffer& targetBuffer) + : buffer_(&targetBuffer) + , maxSiz_(maxSiz) + { } + + + template + BA& + create (SUB&& subMutator) + { + if (sizeof(SUB) > maxSiz_) + throw error::Fatal("Unable to implant implementation object of size " + "exceeding the pre-established storage buffer capacity." + ,error::LUMIERA_ERROR_CAPACITY); + + using Holder = InPlaceBuffer; + Holder& holder = *static_cast (buffer_); + + return holder.create (std::forward (subMutator)); + } + + template + bool + canCreate() const + { + return sizeof(SUB) <= maxSiz_; + } + }; + /////////////////////////////TODO move over into opaque-holder.hpp +namespace diff{ + + namespace error = lumiera::error; + +//using util::_Fmt; + using lib::Literal; + using std::function; + using std::string; + + + + + class TestMutationTarget; // for unit testing + + + namespace { + template + struct Builder; + + using ID = Literal; + using Attribute = DataCap; + } + + + /** + * Customisable intermediary to abstract mutating operations + * on arbitrary, hierarchical object-like data. + * The TreeMutator exposes two distinct interfaces + * - the \em operation API -- similar to what a container exposes -- + * is the entirety of abstract operations that can be done to the + * subsumed, tree like target structure + * - the \em binding API allows to link some or all of these generic + * activities to concrete manipulations known within target scope. + */ + class TreeMutator + { + + public: + + /* ==== operation API ==== */ + + virtual bool + emptySrc () + { + return true; + // do nothing by default + } + + /** skip next src element and advance abstract source position */ + virtual void + skipSrc () + { + // do nothing by default + } + + /** establish new element at current position */ + virtual void + injectNew (GenNode const&) + { + // do nothing by default + } + + /** ensure the next source element matches with given spec */ + virtual bool + matchSrc (GenNode const&) + { + // do nothing by default + return false; + } + + /** accept existing element, when matching the given spec */ + virtual bool + acceptSrc (GenNode const&) + { + // do nothing by default + return false; + } + + /** repeatedly accept, until after the designated location */ + virtual bool + accept_until (GenNode const&) + { + // do nothing by default + return false; + } + + /** locate designated element and accept it at current position */ + virtual bool + findSrc (GenNode const&) + { + // do nothing by default + return false; + } + + /** locate the designated target element + * (must be already accepted into the target sequence). + * Perform an assignement with the given payload value + * @throw when assignement fails (typically error::Logic) + * @return false when unable to locate the target */ + virtual bool + assignElm (GenNode const&) + { + // do nothing by default + return false; + } + + + using MutatorBuffer = PlantingHandle; + + /** locate the designated target element + * and build a suittable sub-mutator for this element + * into the provided target buffer + * @throw error::Fatal when buffer is insufficient + * @return false when unable to locate the target */ + virtual bool + mutateChild (GenNode const&, MutatorBuffer) + { + // do nothing by default + return false; + } + + virtual void setAttribute (ID, Attribute&) { /* do nothing by default */ } + + /** + * start building a custom adapted tree mutator, + * where the operations are tied by closures or + * wrappers into the current implementation context. + */ + static Builder build(); + }; + + + namespace { // Mutator-Builder decorator components... + + using lib::meta::Strip; + + /** + * Type rebinding helper to pick up the actual argument type. + * Works both for functors and for lambda expressions + * @remarks Solution proposed 10/2011 by \link http://stackoverflow.com/users/224671/kennytm user "kennytm" \endlink + * in this \link http://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda/7943765#7943765 + * answer on stackoverflow \endlink + */ + template + struct _ClosureType + : _ClosureType + { }; + + template + struct _ClosureType + { + typedef ARG ArgType; + typedef RET ReturnType; + }; + + + template + struct ChangeOperation + : PAR + { + ID attribID_; + CLO change_; + + virtual void + setAttribute (ID id, Attribute& newValue) + { + using ValueType = typename _ClosureType::ArgType; + + if (id == attribID_) + change_(newValue.get()); + + else // delegate to other closures (Decorator-style) + PAR::setAttribute(id, newValue); + } + + ChangeOperation(ID id, CLO clo, PAR const& chain) + : PAR(chain) + , attribID_(id) + , change_(clo) + { } + }; + + + /** + * Attach to collection: Concrete binding setup. + * This record holds all the actual binding and closures + * used to attach the tree mutator to an external pre-existing + * STL container with child elements/objects. It serves as flexible + * connection, configuration and adaptation element, and will be embedded + * as a whole into the (\ref ChildCollectionMutator), which in turn implements + * the `TreeMutator` interface. The resulting compound is able to consume + * tree diff messages and apply the respective changes and mutations to + * an otherwise opaque implementation data structure. + * + * @tparam COLL a STL compliant collection type holding "child elements" + * @tparam MAT a closure to determine if a child matches a diff spec (GenNode) + * @tparam CTR a closure to construct a new child element from a given diff spec + * @tparam SEL predicate to determine if this binding layer has to process a diff message + * @tparam ASS a closure to assign / set a new value from a given diff spec + * @tparam MUT a closure to construct a nested mutator for some child element + */ + template + struct CollectionBinding + { + using Coll = typename Strip::TypeReferred; + using Elm = typename Coll::value_type; + + Coll& collection; + + SEL isApplicable; + MAT matches; + CTR construct; + ASS assign; + MUT openSub; + + + /* === content manipulation API === */ + + Coll contentBuffer; + +#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + void + initMutation (string mutatorID) + { + prev_content_.clear(); + swap (content_, prev_content_); + log_.event ("attachMutator "+mutatorID); + } + + void + inject (GenNode&& elm, string operationID) + { + content_.emplace_back (forward(elm)); + log_.event (operationID, renderNode (content_.back())); + } + + static iterator + search (GenNode::ID const& targetID, iterator pos) + { + while (pos and not pos->matches(targetID)) + ++pos; + return pos; + } + + iterator + locate (GenNode::ID const& targetID) + { + if (!empty() and content_.back().matches(targetID)) + return lastElm(); + else + return search (targetID, eachElm(content_)); + } +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + }; + + + /** + * Attach to collection: Building block for a concrete `TreeMutator`. + * This decorator will be outfitted with actual binding and closures + * and then layered on top of the (\ref TreeMutaor) base. The resulting + * compound is able to consume tree diff messages and apply the respective + * changes and mutations to an otherwise opaque implementation data structure. + * @remarks in practice, this is the most relevant and typical `TreeMutator` setup. + */ + template + class ChildCollectionMutator + : public PAR + { + BIN binding_; + + public: + ChildCollectionMutator(BIN wiringClosures, PAR const& chain) + : PAR(chain) + , binding_(wiringClosures) + { } + + + + /* ==== re-Implementation of the operation API ==== */ + +#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + /** skip next recorded src element + * @remarks TestWireTap adapter together with TestMutationTarget + * maintain a "shaddow copy" of the data and apply the detected diff + * against this internal copy. This allows to verify what's going on + */ + virtual void + skipSrc () override + { + if (pos_) + { + GenNode const& skippedElm = *pos_; + ++pos_; + target_.logSkip (skippedElm); + } + PAR::skipSrc(); + } + + /** record in the test taget + * that a new child element is + * being insertet at current position + */ + virtual void + injectNew (GenNode const& n) override + { + target_.inject (GenNode{n}, "injectNew"); + PAR::injectNew (n); + } + + virtual bool + emptySrc () override + { + return !pos_ + and PAR::emptySrc(); + } + + /** ensure the next recorded source element + * matches on a formal level with given spec */ + virtual bool + matchSrc (GenNode const& n) override + { + return PAR::matchSrc(n) + or pos_? n.matches(*pos_) + : false; + } + + /** accept existing element, when matching the given spec */ + virtual bool + acceptSrc (GenNode const& n) override + { + bool isSrcMatch = TestWireTap::matchSrc(n); + if (isSrcMatch) // NOTE: important to call our own method here, not the virtual function + { + target_.inject (move(*pos_), "acceptSrc"); + ++pos_; + } + return PAR::acceptSrc(n) + or isSrcMatch; + } + + /** locate designated element and accept it at current position */ + virtual bool + findSrc (GenNode const& ref) override + { + Iter found = TestMutationTarget::search (ref.idi, pos_); + if (found) + { + target_.inject (move(*found), "findSrc"); + } + return PAR::findSrc(ref) + or found; + } + + /** repeatedly accept, until after the designated location */ + virtual bool + accept_until (GenNode const& spec) + { + bool foundTarget = true; + + if (spec.matches (Ref::END)) + for ( ; pos_; ++pos_) + target_.inject (move(*pos_), "accept_until END"); + else + { + string logMsg{"accept_until "+spec.idi.getSym()}; + while (pos_ and not TestWireTap::matchSrc(spec)) + { + target_.inject (move(*pos_), logMsg); + ++pos_; + } + if (TestWireTap::matchSrc(spec)) + { + target_.inject (move(*pos_), logMsg); + ++pos_; + } + else + foundTarget = false; + } + return PAR::accept_until(spec) + or foundTarget; + } + + /** locate element already accepted into the target sequence + * and assign the designated payload value to it. */ + virtual bool + assignElm (GenNode const& spec) + { + Iter targetElm = target_.locate (spec.idi); + if (targetElm) + { + string logOldPayload{render(targetElm->data)}; + *targetElm = spec; + target_.logAssignment (*targetElm, logOldPayload); + } + return PAR::assignElm(spec) + or targetElm; + } + + /** locate the designated target element and build a suitable + * sub-mutator for this element into the provided target buffer */ + virtual bool + mutateChild (GenNode const& spec, TreeMutator::MutatorBuffer targetBuff) + { + if (PAR::mutateChild (spec, targetBuff)) + return true; + else // Test mode only -- + { // no other layer was able to provide a mutator + Iter targetElm = target_.locate (spec.idi); + if (targetElm) + { + targetBuff.create (TreeMutator::build()); + target_.logMutation (*targetElm); + return true; + } + return false; + } + } +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #992 + }; + + + /** + * Nested DSL to define the specifics of a collection binding. + */ + template + struct CollectionBindingBuilder + : CollectionBinding + { + + template + CollectionBindingBuilder + matchElement(FUN matcher) + { + return { this->collection + , matcher + , this->construct + , this->isApplicable + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + constructFrom(FUN constructor) + { + return { this->collection + , this->matches + , constructor + , this->isApplicable + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + isApplicableIf(FUN selector) + { + return { this->collection + , this->matches + , this->construct + , selector + , this->assign + , this->openSub + }; + } + + template + CollectionBindingBuilder + assignElement(FUN setter) + { + return { this->collection + , this->matches + , this->construct + , this->isApplicable + , setter + , this->openSub + }; + } + + template + CollectionBindingBuilder + buildChildMutator(FUN childMutationBuilder) + { + return { this->collection + , this->matches + , this->construct + , this->isApplicable + , this->assign + , childMutationBuilder + }; + } + }; + + template + struct _DefaultBinding + { + using Coll = typename Strip::TypeReferred; + using Elm = typename Coll::value_type; + + static bool + disable_selector (GenNode const& spec) + { + UNIMPLEMENTED ("dont discriminate by default"); + } + + static bool + default_contantMatch (Elm const& elm) + { + UNIMPLEMENTED ("fallback matcher"); + } + + static Elm + default_construct_from_payload (GenNode const& spec) + { + UNIMPLEMENTED ("default construct from spec payload"); + } + + static bool + disable_assignment (GenNode const& spec) + { + UNIMPLEMENTED ("disabled assignment"); + } + + static bool + disable_childMutation (GenNode const& spec, TreeMutator::MutatorBuffer targetBuff) + { + UNIMPLEMENTED ("inactive mutator builder"); + } + + + using FallbackBindingConfiguration + = CollectionBindingBuilder; + + static FallbackBindingConfiguration + attachTo (Coll& coll) + { + return { coll + , disable_selector + , default_contantMatch + , default_construct_from_payload + , disable_assignment + , disable_childMutation + }; + } + }; + + + template + auto + collection (COLL& coll) -> decltype(_DefaultBinding::attachTo(coll)) + { + return _DefaultBinding::attachTo(coll); + } + + + + + template + struct TestWireTap; + + + template + struct Builder + : PAR + { + Builder(PAR par) + : PAR(par) + { } + + template + using Change = ChangeOperation; + + template + using Collection = ChildCollectionMutator; + + using WireTap = TestWireTap; + + + /* ==== binding API ==== */ + + template + Builder> + change (Literal attributeID, CLO closure) + { + return Change (attributeID, closure, *this); + } + + template + Builder> + attach (BIN&& collectionBindingSetup) + { + return Collection (std::forward(collectionBindingSetup)); + } + + Builder + attachDummy (TestMutationTarget& dummy); + + }; + + }//(END) Mutator-Builder decorator components... + + + Builder + TreeMutator::build () + { + return TreeMutator(); + } + + +}} // namespace lib::diff +#endif /*LIB_DIFF_TREE_MUTATOR_COLLECTION_BINDING_H*/ diff --git a/src/lib/diff/tree-mutator.hpp b/src/lib/diff/tree-mutator.hpp index 736586ad6..a94b702de 100644 --- a/src/lib/diff/tree-mutator.hpp +++ b/src/lib/diff/tree-mutator.hpp @@ -58,6 +58,9 @@ ** API is only declared (forward) by default. The TestMutationTarget is a helper class, ** which can be attached through this binding and allows a unit test fixture to record ** and verify all the mutation operations encountered. + ** + ** @note to improve readability, the actual implementation of the "binding layers" + ** is defined in separate headers and included towards the bottom of this header. ** ** @see tree-mutator-test.cpp ** @see DiffDetector @@ -752,4 +755,10 @@ namespace diff{ }} // namespace lib::diff + + // NOTE: including implementation details... +// +#include "lib/diff/tree-mutator-attribute-binding.hpp" +#include "lib/diff/tree-mutator-collection-binding.hpp" + #endif /*LIB_DIFF_TREE_MUTATOR_H*/